├── example ├── .watchmanconfig ├── .bundle │ └── config ├── app.json ├── babel.config.js ├── android │ ├── app │ │ ├── debug.keystore │ │ ├── src │ │ │ ├── main │ │ │ │ ├── res │ │ │ │ │ ├── values │ │ │ │ │ │ ├── strings.xml │ │ │ │ │ │ └── styles.xml │ │ │ │ │ ├── mipmap-hdpi │ │ │ │ │ │ ├── ic_launcher.png │ │ │ │ │ │ └── ic_launcher_round.png │ │ │ │ │ ├── mipmap-mdpi │ │ │ │ │ │ ├── ic_launcher.png │ │ │ │ │ │ └── ic_launcher_round.png │ │ │ │ │ ├── mipmap-xhdpi │ │ │ │ │ │ ├── ic_launcher.png │ │ │ │ │ │ └── ic_launcher_round.png │ │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ │ │ ├── ic_launcher.png │ │ │ │ │ │ └── ic_launcher_round.png │ │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ │ │ ├── ic_launcher.png │ │ │ │ │ │ └── ic_launcher_round.png │ │ │ │ │ └── drawable │ │ │ │ │ │ └── rn_edit_text_material.xml │ │ │ │ ├── AndroidManifest.xml │ │ │ │ └── java │ │ │ │ │ └── com │ │ │ │ │ └── nitroimageexample │ │ │ │ │ ├── MainActivity.kt │ │ │ │ │ └── MainApplication.kt │ │ │ └── debug │ │ │ │ └── AndroidManifest.xml │ │ └── proguard-rules.pro │ ├── gradle │ │ └── wrapper │ │ │ ├── gradle-wrapper.jar │ │ │ └── gradle-wrapper.properties │ ├── settings.gradle │ ├── build.gradle │ └── gradle.properties ├── ios │ ├── NitroImageExample │ │ ├── Images.xcassets │ │ │ ├── Contents.json │ │ │ └── AppIcon.appiconset │ │ │ │ └── Contents.json │ │ ├── PrivacyInfo.xcprivacy │ │ ├── AppDelegate.swift │ │ └── Info.plist │ ├── NitroImageExample.xcworkspace │ │ └── contents.xcworkspacedata │ ├── .xcode.env │ └── Podfile ├── tsconfig.json ├── index.js ├── src │ ├── createImageURLs.ts │ ├── App.tsx │ ├── FastImageTab.tsx │ ├── NitroImageTab.tsx │ └── EmptyTab.tsx ├── Gemfile ├── metro.config.js ├── .gitignore └── package.json ├── bunfig.toml ├── packages ├── react-native-nitro-image │ ├── nitrogen │ │ └── generated │ │ │ ├── .gitattributes │ │ │ ├── shared │ │ │ ├── json │ │ │ │ └── NitroImageViewConfig.json │ │ │ └── c++ │ │ │ │ ├── HybridImageLoaderSpec.cpp │ │ │ │ ├── HybridImageUtilsSpec.cpp │ │ │ │ ├── HybridNitroImageViewSpec.cpp │ │ │ │ ├── HybridImageLoaderFactorySpec.cpp │ │ │ │ ├── HybridImageUtilsSpec.hpp │ │ │ │ ├── HybridImageFactorySpec.cpp │ │ │ │ └── HybridImageSpec.cpp │ │ │ ├── ios │ │ │ ├── c++ │ │ │ │ ├── HybridImageSpecSwift.cpp │ │ │ │ ├── HybridImageLoaderSpecSwift.cpp │ │ │ │ ├── HybridImageUtilsSpecSwift.cpp │ │ │ │ ├── HybridImageFactorySpecSwift.cpp │ │ │ │ ├── HybridNitroImageViewSpecSwift.cpp │ │ │ │ └── HybridImageLoaderFactorySpecSwift.cpp │ │ │ ├── swift │ │ │ │ ├── Variant__any_HybridImageSpec___any_HybridImageLoaderSpec_.swift │ │ │ │ ├── ImageFormat.swift │ │ │ │ ├── ResizeMode.swift │ │ │ │ ├── Func_void.swift │ │ │ │ ├── Func_void_std__string.swift │ │ │ │ ├── Func_void_RawPixelData.swift │ │ │ │ ├── RawPixelData.swift │ │ │ │ ├── Func_void_EncodedImageData.swift │ │ │ │ ├── Func_void_std__exception_ptr.swift │ │ │ │ ├── EncodedImageData.swift │ │ │ │ ├── Func_void_std__shared_ptr_ArrayBuffer_.swift │ │ │ │ ├── HybridImageLoaderSpec.swift │ │ │ │ ├── HybridImageUtilsSpec.swift │ │ │ │ ├── HybridNitroImageViewSpec.swift │ │ │ │ ├── Color.swift │ │ │ │ ├── PixelFormat.swift │ │ │ │ ├── Func_void_std__shared_ptr_HybridImageSpec_.swift │ │ │ │ └── HybridImageLoaderFactorySpec.swift │ │ │ ├── NitroImageAutolinking.mm │ │ │ └── NitroImage+autolinking.rb │ │ │ └── android │ │ │ ├── kotlin │ │ │ └── com │ │ │ │ └── margelo │ │ │ │ └── nitro │ │ │ │ └── image │ │ │ │ ├── ImageFormat.kt │ │ │ │ ├── ResizeMode.kt │ │ │ │ ├── PixelFormat.kt │ │ │ │ ├── views │ │ │ │ ├── HybridNitroImageViewStateUpdater.kt │ │ │ │ └── HybridNitroImageViewManager.kt │ │ │ │ ├── Color.kt │ │ │ │ ├── RawPixelData.kt │ │ │ │ ├── NitroImageOnLoad.kt │ │ │ │ ├── EncodedImageData.kt │ │ │ │ ├── HybridImageLoaderSpec.kt │ │ │ │ ├── HybridNitroImageViewSpec.kt │ │ │ │ ├── HybridImageUtilsSpec.kt │ │ │ │ ├── Variant_HybridImageSpec_HybridImageLoaderSpec.kt │ │ │ │ └── HybridImageLoaderFactorySpec.kt │ │ │ ├── NitroImageOnLoad.hpp │ │ │ ├── NitroImage+autolinking.gradle │ │ │ └── c++ │ │ │ ├── JVariant_HybridImageSpec_HybridImageLoaderSpec.cpp │ │ │ ├── views │ │ │ └── JHybridNitroImageViewStateUpdater.hpp │ │ │ └── JImageFormat.hpp │ ├── babel.config.js │ ├── android │ │ ├── src │ │ │ └── main │ │ │ │ ├── AndroidManifest.xml │ │ │ │ ├── cpp │ │ │ │ └── cpp-adapter.cpp │ │ │ │ └── java │ │ │ │ └── com │ │ │ │ └── margelo │ │ │ │ └── nitro │ │ │ │ └── image │ │ │ │ ├── ArrayBuffer+copyIfNotOwner.kt │ │ │ │ ├── Bitmap+isGPU.kt │ │ │ │ ├── Bitmap+toCpuAccessible.kt │ │ │ │ ├── Color+toBitmapColor.kt │ │ │ │ ├── Bitmap+toByteBuffer.kt │ │ │ │ ├── Bitmap+toMutable.kt │ │ │ │ ├── CustomImageView.kt │ │ │ │ ├── HybridImageLoader.kt │ │ │ │ ├── Bitmap+compressInMemory.kt │ │ │ │ ├── ArrayBuffer+toByteArray.kt │ │ │ │ ├── FastByteArrayOutputStream.kt │ │ │ │ ├── Bitmap+saveToFile.kt │ │ │ │ ├── HybridImageLoaderFactory.kt │ │ │ │ ├── HybridImageUtils.kt │ │ │ │ ├── NitroImagePackage.java │ │ │ │ └── Bitmap+pixelFormat.kt │ │ ├── gradle.properties │ │ └── CMakeLists.txt │ ├── ios │ │ ├── Bridge.h │ │ ├── HybridImage.swift │ │ ├── Color+toUIColor.swift │ │ ├── ImageFormat+toUTType.swift │ │ ├── NativeImageView.swift │ │ ├── UIImage+memorySize.swift │ │ ├── UIImage+toEncodedImageData.swift │ │ ├── UIImageOrientation+fromDegrees.swift │ │ ├── HybridImageUtils.swift │ │ ├── CGImage+getPixelFormat.swift │ │ ├── HybridImageLoaderFactory.swift │ │ ├── UIImage+getData.swift │ │ └── HybridImageLoader.swift │ ├── tsconfig.build.json │ ├── tsconfig.json │ ├── src │ │ ├── Images.ts │ │ ├── ImageLoaders.ts │ │ ├── index.ts │ │ ├── specs │ │ │ ├── ImageLoaderFactory.nitro.ts │ │ │ ├── ImageUtils.nitro.ts │ │ │ └── ImageLoader.nitro.ts │ │ ├── ImageUtils.ts │ │ ├── NativeNitroImage.tsx │ │ ├── useImageLoader.ts │ │ ├── markHybridObject.ts │ │ ├── OptionalWebLoader.ts │ │ ├── NitroImage.tsx │ │ ├── AsyncImageSource.ts │ │ └── useImage.ts │ ├── react-native.config.js │ ├── nitro.json │ └── NitroImage.podspec └── react-native-nitro-web-image │ ├── nitrogen │ └── generated │ │ ├── .gitattributes │ │ ├── ios │ │ ├── c++ │ │ │ └── HybridWebImageFactorySpecSwift.cpp │ │ ├── NitroWebImageAutolinking.mm │ │ ├── NitroWebImageAutolinking.swift │ │ └── swift │ │ │ ├── AsyncImagePriority.swift │ │ │ ├── Func_void_std__exception_ptr.swift │ │ │ └── HybridWebImageFactorySpec.swift │ │ ├── android │ │ ├── kotlin │ │ │ └── com │ │ │ │ └── margelo │ │ │ │ └── nitro │ │ │ │ └── web │ │ │ │ └── image │ │ │ │ ├── AsyncImagePriority.kt │ │ │ │ ├── NitroWebImageOnLoad.kt │ │ │ │ ├── AsyncImageLoadOptions.kt │ │ │ │ └── HybridWebImageFactorySpec.kt │ │ ├── NitroWebImage+autolinking.gradle │ │ ├── NitroWebImageOnLoad.hpp │ │ └── NitroWebImageOnLoad.cpp │ │ └── shared │ │ └── c++ │ │ └── HybridWebImageFactorySpec.cpp │ ├── babel.config.js │ ├── android │ ├── src │ │ └── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── cpp │ │ │ └── cpp-adapter.cpp │ │ │ └── java │ │ │ └── com │ │ │ └── margelo │ │ │ └── nitro │ │ │ └── web │ │ │ └── image │ │ │ ├── AsyncImagePriority+toCoroutineContext.kt │ │ │ ├── HybridWebImageLoader.kt │ │ │ ├── NitroWebImagePackage.java │ │ │ ├── ImageLoader+loadImageAsync.kt │ │ │ ├── HybridWebImageFactory.kt │ │ │ └── ImageRequestBuilder+applyOptions.kt │ ├── gradle.properties │ └── CMakeLists.txt │ ├── ios │ ├── Bridge.h │ ├── SDWebImageManager+loadImage.swift │ ├── HybridWebImageFactory.swift │ ├── AsyncImageLoadOptions+toSDWebImageOptions.swift │ └── HybridWebImageLoader.swift │ ├── src │ └── index.ts │ ├── tsconfig.build.json │ ├── tsconfig.json │ ├── react-native.config.js │ ├── nitro.json │ └── NitroWebImage.podspec ├── img ├── banner-dark.png └── banner-light.png ├── .github ├── FUNDING.yml └── workflows │ ├── lint.yml │ ├── run-nitrogen.yml │ ├── build-android-release.yml │ └── build-android.yml ├── scripts ├── try-install-lockfiles.sh └── release.sh ├── config └── tsconfig.json ├── biome.json ├── .gitignore └── LICENSE /example/.watchmanconfig: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /bunfig.toml: -------------------------------------------------------------------------------- 1 | [install] 2 | # Opt out from isolated installs 3 | linker = "hoisted" 4 | -------------------------------------------------------------------------------- /example/.bundle/config: -------------------------------------------------------------------------------- 1 | BUNDLE_PATH: "vendor/bundle" 2 | BUNDLE_FORCE_RUBY_PLATFORM: 1 3 | -------------------------------------------------------------------------------- /packages/react-native-nitro-image/nitrogen/generated/.gitattributes: -------------------------------------------------------------------------------- 1 | ** linguist-generated=true 2 | -------------------------------------------------------------------------------- /packages/react-native-nitro-web-image/nitrogen/generated/.gitattributes: -------------------------------------------------------------------------------- 1 | ** linguist-generated=true 2 | -------------------------------------------------------------------------------- /example/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "NitroImageExample", 3 | "displayName": "NitroImageExample" 4 | } 5 | -------------------------------------------------------------------------------- /img/banner-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrousavy/react-native-nitro-image/HEAD/img/banner-dark.png -------------------------------------------------------------------------------- /example/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ["module:@react-native/babel-preset"], 3 | }; 4 | -------------------------------------------------------------------------------- /img/banner-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrousavy/react-native-nitro-image/HEAD/img/banner-light.png -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: mrousavy 4 | ko_fi: mrousavy 5 | -------------------------------------------------------------------------------- /example/android/app/debug.keystore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrousavy/react-native-nitro-image/HEAD/example/android/app/debug.keystore -------------------------------------------------------------------------------- /example/ios/NitroImageExample/Images.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info": { 3 | "version": 1, 4 | "author": "xcode" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /packages/react-native-nitro-image/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ["module:@react-native/babel-preset"], 3 | }; 4 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | NitroImageExample 3 | 4 | -------------------------------------------------------------------------------- /packages/react-native-nitro-web-image/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ["module:@react-native/babel-preset"], 3 | }; 4 | -------------------------------------------------------------------------------- /packages/react-native-nitro-image/android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /example/android/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrousavy/react-native-nitro-image/HEAD/example/android/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /packages/react-native-nitro-web-image/android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /packages/react-native-nitro-image/ios/Bridge.h: -------------------------------------------------------------------------------- 1 | // 2 | // Bridge.h 3 | // NitroImage 4 | // 5 | // Created by Marc Rousavy on 22.07.24. 6 | // 7 | 8 | #pragma once 9 | -------------------------------------------------------------------------------- /example/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@react-native/typescript-config", 3 | "include": ["**/*.ts", "**/*.tsx"], 4 | "exclude": ["**/node_modules", "**/Pods"] 5 | } 6 | -------------------------------------------------------------------------------- /packages/react-native-nitro-web-image/ios/Bridge.h: -------------------------------------------------------------------------------- 1 | // 2 | // Bridge.h 3 | // NitroWebImage 4 | // 5 | // Created by Marc Rousavy on 22.07.24. 6 | // 7 | 8 | #pragma once 9 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrousavy/react-native-nitro-image/HEAD/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrousavy/react-native-nitro-image/HEAD/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrousavy/react-native-nitro-image/HEAD/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrousavy/react-native-nitro-image/HEAD/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrousavy/react-native-nitro-image/HEAD/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrousavy/react-native-nitro-image/HEAD/example/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrousavy/react-native-nitro-image/HEAD/example/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrousavy/react-native-nitro-image/HEAD/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrousavy/react-native-nitro-image/HEAD/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrousavy/react-native-nitro-image/HEAD/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /packages/react-native-nitro-image/android/gradle.properties: -------------------------------------------------------------------------------- 1 | NitroImage_kotlinVersion=2.0.21 2 | NitroImage_minSdkVersion=23 3 | NitroImage_targetSdkVersion=35 4 | NitroImage_compileSdkVersion=34 5 | NitroImage_ndkVersion=27.1.12297006 6 | -------------------------------------------------------------------------------- /packages/react-native-nitro-web-image/android/gradle.properties: -------------------------------------------------------------------------------- 1 | NitroImage_kotlinVersion=2.0.21 2 | NitroImage_minSdkVersion=23 3 | NitroImage_targetSdkVersion=35 4 | NitroImage_compileSdkVersion=34 5 | NitroImage_ndkVersion=27.1.12297006 6 | -------------------------------------------------------------------------------- /example/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @format 3 | */ 4 | 5 | import { AppRegistry } from "react-native"; 6 | import { name as appName } from "./app.json"; 7 | import App from "./src/App"; 8 | 9 | AppRegistry.registerComponent(appName, () => App); 10 | -------------------------------------------------------------------------------- /packages/react-native-nitro-image/android/src/main/cpp/cpp-adapter.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "NitroImageOnLoad.hpp" 3 | 4 | JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void*) { 5 | return margelo::nitro::image::initialize(vm); 6 | } 7 | -------------------------------------------------------------------------------- /packages/react-native-nitro-web-image/android/src/main/cpp/cpp-adapter.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "NitroWebImageOnLoad.hpp" 3 | 4 | JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void*) { 5 | return margelo::nitro::web::image::initialize(vm); 6 | } 7 | -------------------------------------------------------------------------------- /packages/react-native-nitro-web-image/src/index.ts: -------------------------------------------------------------------------------- 1 | import { NitroModules } from "react-native-nitro-modules"; 2 | import type { WebImageFactory } from "./specs/WebImageFactory.nitro"; 3 | 4 | export const WebImages = 5 | NitroModules.createHybridObject("WebImageFactory"); 6 | -------------------------------------------------------------------------------- /packages/react-native-nitro-image/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "@tsconfig/react-native/tsconfig.json", 4 | "../../config/tsconfig.json" 5 | ], 6 | "include": ["src"], 7 | "compilerOptions": { 8 | "rootDir": "src", 9 | "jsx": "react" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /packages/react-native-nitro-web-image/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "@tsconfig/react-native/tsconfig.json", 4 | "../../config/tsconfig.json" 5 | ], 6 | "include": ["src"], 7 | "compilerOptions": { 8 | "rootDir": "src", 9 | "jsx": "react" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /example/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /example/ios/NitroImageExample.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /example/src/createImageURLs.ts: -------------------------------------------------------------------------------- 1 | const DEFAULT_COUNT = 1000; 2 | 3 | export function createImageURLs( 4 | count: number = DEFAULT_COUNT, 5 | size = 800, 6 | ): string[] { 7 | return [...Array(count).fill(undefined)].map((_, index) => { 8 | return `https://picsum.photos/seed/${index + 1}/${size}`; 9 | }); 10 | } 11 | -------------------------------------------------------------------------------- /packages/react-native-nitro-image/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "@tsconfig/react-native/tsconfig.json", 4 | "../../config/tsconfig.json" 5 | ], 6 | "include": ["src"], 7 | "compilerOptions": { 8 | "rootDir": "src", 9 | "outDir": "lib", 10 | "jsx": "react" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /packages/react-native-nitro-image/src/Images.ts: -------------------------------------------------------------------------------- 1 | import { NitroModules } from "react-native-nitro-modules"; 2 | import type { ImageFactory } from "./specs/ImageFactory.nitro"; 3 | 4 | /** 5 | * A factory for loading and creating `Image` instances. 6 | */ 7 | export const Images = 8 | NitroModules.createHybridObject("ImageFactory"); 9 | -------------------------------------------------------------------------------- /packages/react-native-nitro-web-image/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "@tsconfig/react-native/tsconfig.json", 4 | "../../config/tsconfig.json" 5 | ], 6 | "include": ["src"], 7 | "compilerOptions": { 8 | "rootDir": "src", 9 | "outDir": "lib", 10 | "jsx": "react" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /packages/react-native-nitro-image/android/src/main/java/com/margelo/nitro/image/ArrayBuffer+copyIfNotOwner.kt: -------------------------------------------------------------------------------- 1 | package com.margelo.nitro.image 2 | 3 | import com.margelo.nitro.core.ArrayBuffer 4 | 5 | fun ArrayBuffer.copyIfNotOwner(): ArrayBuffer { 6 | if (!this.isOwner) { 7 | return ArrayBuffer.copy(this) 8 | } 9 | return this 10 | } 11 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /packages/react-native-nitro-image/src/ImageLoaders.ts: -------------------------------------------------------------------------------- 1 | import { NitroModules } from "react-native-nitro-modules"; 2 | import type { ImageLoaderFactory } from "./specs/ImageLoaderFactory.nitro"; 3 | 4 | /** 5 | * A factory for creating `ImageLoader` instances. 6 | */ 7 | export const ImageLoaders = 8 | NitroModules.createHybridObject("ImageLoaderFactory"); 9 | -------------------------------------------------------------------------------- /packages/react-native-nitro-image/android/src/main/java/com/margelo/nitro/image/Bitmap+isGPU.kt: -------------------------------------------------------------------------------- 1 | package com.margelo.nitro.image 2 | 3 | import android.graphics.Bitmap 4 | import android.os.Build 5 | 6 | val Bitmap.isGPU: Boolean 7 | get() { 8 | return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && 9 | this.config == Bitmap.Config.HARDWARE 10 | } 11 | -------------------------------------------------------------------------------- /packages/react-native-nitro-image/nitrogen/generated/shared/json/NitroImageViewConfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "uiViewClassName": "NitroImageView", 3 | "supportsRawText": false, 4 | "bubblingEventTypes": {}, 5 | "directEventTypes": {}, 6 | "validAttributes": { 7 | "image": true, 8 | "resizeMode": true, 9 | "recyclingKey": true, 10 | "hybridRef": true 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /example/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 9 | 10 | -------------------------------------------------------------------------------- /example/android/settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { includeBuild("../../node_modules/@react-native/gradle-plugin") } 2 | plugins { id("com.facebook.react.settings") } 3 | extensions.configure(com.facebook.react.ReactSettingsExtension){ ex -> ex.autolinkLibrariesFromCommand() } 4 | rootProject.name = 'NitroImageExample' 5 | include ':app' 6 | includeBuild('../../node_modules/@react-native/gradle-plugin') 7 | -------------------------------------------------------------------------------- /packages/react-native-nitro-image/ios/HybridImage.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HybridImage.swift 3 | // react-native-nitro-image 4 | // 5 | // Created by Marc Rousavy on 10.06.25. 6 | // 7 | 8 | import Foundation 9 | import UIKit 10 | import NitroModules 11 | 12 | class HybridImage: HybridImageSpec, NativeImage { 13 | let uiImage: UIImage 14 | 15 | init(uiImage: UIImage) { 16 | self.uiImage = uiImage 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/react-native-nitro-image/nitrogen/generated/ios/c++/HybridImageSpecSwift.cpp: -------------------------------------------------------------------------------- 1 | /// 2 | /// HybridImageSpecSwift.cpp 3 | /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. 4 | /// https://github.com/mrousavy/nitro 5 | /// Copyright © 2025 Marc Rousavy @ Margelo 6 | /// 7 | 8 | #include "HybridImageSpecSwift.hpp" 9 | 10 | namespace margelo::nitro::image { 11 | } // namespace margelo::nitro::image 12 | -------------------------------------------------------------------------------- /packages/react-native-nitro-image/nitrogen/generated/ios/c++/HybridImageLoaderSpecSwift.cpp: -------------------------------------------------------------------------------- 1 | /// 2 | /// HybridImageLoaderSpecSwift.cpp 3 | /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. 4 | /// https://github.com/mrousavy/nitro 5 | /// Copyright © 2025 Marc Rousavy @ Margelo 6 | /// 7 | 8 | #include "HybridImageLoaderSpecSwift.hpp" 9 | 10 | namespace margelo::nitro::image { 11 | } // namespace margelo::nitro::image 12 | -------------------------------------------------------------------------------- /packages/react-native-nitro-image/nitrogen/generated/ios/c++/HybridImageUtilsSpecSwift.cpp: -------------------------------------------------------------------------------- 1 | /// 2 | /// HybridImageUtilsSpecSwift.cpp 3 | /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. 4 | /// https://github.com/mrousavy/nitro 5 | /// Copyright © 2025 Marc Rousavy @ Margelo 6 | /// 7 | 8 | #include "HybridImageUtilsSpecSwift.hpp" 9 | 10 | namespace margelo::nitro::image { 11 | } // namespace margelo::nitro::image 12 | -------------------------------------------------------------------------------- /packages/react-native-nitro-image/android/src/main/java/com/margelo/nitro/image/Bitmap+toCpuAccessible.kt: -------------------------------------------------------------------------------- 1 | package com.margelo.nitro.image 2 | 3 | import android.graphics.Bitmap 4 | 5 | fun Bitmap.toCpuAccessible(): Bitmap { 6 | if (this.config == Bitmap.Config.HARDWARE) { 7 | // HARDWARE isn't CPU-accessible, so we convert to ARGB 8 | return this.copy(Bitmap.Config.ARGB_8888, true) 9 | } 10 | return this 11 | } 12 | -------------------------------------------------------------------------------- /packages/react-native-nitro-image/nitrogen/generated/ios/c++/HybridImageFactorySpecSwift.cpp: -------------------------------------------------------------------------------- 1 | /// 2 | /// HybridImageFactorySpecSwift.cpp 3 | /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. 4 | /// https://github.com/mrousavy/nitro 5 | /// Copyright © 2025 Marc Rousavy @ Margelo 6 | /// 7 | 8 | #include "HybridImageFactorySpecSwift.hpp" 9 | 10 | namespace margelo::nitro::image { 11 | } // namespace margelo::nitro::image 12 | -------------------------------------------------------------------------------- /packages/react-native-nitro-image/nitrogen/generated/ios/c++/HybridNitroImageViewSpecSwift.cpp: -------------------------------------------------------------------------------- 1 | /// 2 | /// HybridNitroImageViewSpecSwift.cpp 3 | /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. 4 | /// https://github.com/mrousavy/nitro 5 | /// Copyright © 2025 Marc Rousavy @ Margelo 6 | /// 7 | 8 | #include "HybridNitroImageViewSpecSwift.hpp" 9 | 10 | namespace margelo::nitro::image { 11 | } // namespace margelo::nitro::image 12 | -------------------------------------------------------------------------------- /packages/react-native-nitro-image/ios/Color+toUIColor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Color+toUIColor.swift 3 | // react-native-nitro-image 4 | // 5 | // Created by Marc Rousavy on 05.11.25. 6 | // 7 | 8 | import Foundation 9 | import UIKit 10 | 11 | extension Color { 12 | func toUIColor() -> UIColor { 13 | return UIColor(red: r, 14 | green: g, 15 | blue: b, 16 | alpha: a ?? 1.0) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /scripts/try-install-lockfiles.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Do not exit on errors 4 | set +e 5 | 6 | cd "$(pwd)" || exit 0 7 | 8 | # Run commands and ignore any errors 9 | { 10 | # Install JS lockfiles 11 | bun i 12 | bun run build 13 | 14 | # Install example pods 15 | bun example bundle-install 16 | bun example pods 17 | 18 | # Add everything to git 19 | git add **/*.lock 20 | } || true 21 | 22 | # No errors - whatever. 23 | exit 0 24 | -------------------------------------------------------------------------------- /packages/react-native-nitro-image/nitrogen/generated/ios/c++/HybridImageLoaderFactorySpecSwift.cpp: -------------------------------------------------------------------------------- 1 | /// 2 | /// HybridImageLoaderFactorySpecSwift.cpp 3 | /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. 4 | /// https://github.com/mrousavy/nitro 5 | /// Copyright © 2025 Marc Rousavy @ Margelo 6 | /// 7 | 8 | #include "HybridImageLoaderFactorySpecSwift.hpp" 9 | 10 | namespace margelo::nitro::image { 11 | } // namespace margelo::nitro::image 12 | -------------------------------------------------------------------------------- /packages/react-native-nitro-web-image/nitrogen/generated/ios/c++/HybridWebImageFactorySpecSwift.cpp: -------------------------------------------------------------------------------- 1 | /// 2 | /// HybridWebImageFactorySpecSwift.cpp 3 | /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. 4 | /// https://github.com/mrousavy/nitro 5 | /// Copyright © 2025 Marc Rousavy @ Margelo 6 | /// 7 | 8 | #include "HybridWebImageFactorySpecSwift.hpp" 9 | 10 | namespace margelo::nitro::web::image { 11 | } // namespace margelo::nitro::web::image 12 | -------------------------------------------------------------------------------- /packages/react-native-nitro-image/android/src/main/java/com/margelo/nitro/image/Color+toBitmapColor.kt: -------------------------------------------------------------------------------- 1 | package com.margelo.nitro.image 2 | 3 | fun Color.toBitmapColor(): Int { 4 | if (a != null) { 5 | // We have an alpha Channel 6 | return android.graphics.Color.argb(a.toFloat(), r.toFloat(), g.toFloat(), b.toFloat()) 7 | } else { 8 | // We don't have an alpha channel 9 | return android.graphics.Color.rgb(r.toFloat(), g.toFloat(), b.toFloat()) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /packages/react-native-nitro-image/ios/ImageFormat+toUTType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImageFormat+toUTType.swift 3 | // react-native-nitro-image 4 | // 5 | // Created by Marc Rousavy on 11.06.25. 6 | // 7 | 8 | import Foundation 9 | import UniformTypeIdentifiers 10 | 11 | extension ImageFormat { 12 | func toUTType() -> UTType { 13 | switch self { 14 | case .jpg: 15 | return .jpeg 16 | case .png: 17 | return .png 18 | case .heic: 19 | return .heic 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /packages/react-native-nitro-image/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./createImageLoader"; 2 | export * from "./Images"; 3 | export * from "./ImageUtils"; 4 | export * from "./loadImage"; 5 | export { NativeNitroImage } from "./NativeNitroImage"; 6 | export { NitroImage, type NitroImageProps } from "./NitroImage"; 7 | export type { Image } from "./specs/Image.nitro"; 8 | export type { ImageLoader } from "./specs/ImageLoader.nitro"; 9 | 10 | export * from "./useImage"; 11 | export * from "./useImageLoader"; 12 | -------------------------------------------------------------------------------- /example/android/app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /usr/local/Cellar/android-sdk/24.3.3/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | -------------------------------------------------------------------------------- /scripts/release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | echo "Starting the release process..." 6 | echo "Provided options: $@" 7 | 8 | echo "Publishing 'react-native-nitro-image' to NPM" 9 | cd packages/react-native-nitro-image 10 | bun release $@ 11 | 12 | echo "Publishing 'react-native-nitro-web-image' to NPM" 13 | cd ../react-native-nitro-web-image 14 | bun release $@ 15 | 16 | echo "Creating a Git bump commit and GitHub release" 17 | cd ../.. 18 | bun run release-it $@ 19 | 20 | echo "Successfully released NitroImage!" 21 | -------------------------------------------------------------------------------- /example/ios/.xcode.env: -------------------------------------------------------------------------------- 1 | # This `.xcode.env` file is versioned and is used to source the environment 2 | # used when running script phases inside Xcode. 3 | # To customize your local environment, you can create an `.xcode.env.local` 4 | # file that is not versioned. 5 | 6 | # NODE_BINARY variable contains the PATH to the node executable. 7 | # 8 | # Customize the NODE_BINARY variable here. 9 | # For example, to use nvm with brew, add the following line 10 | # . "$(brew --prefix nvm)/nvm.sh" --no-use 11 | export NODE_BINARY=$(command -v node) 12 | -------------------------------------------------------------------------------- /packages/react-native-nitro-image/react-native.config.js: -------------------------------------------------------------------------------- 1 | // https://github.com/react-native-community/cli/blob/main/docs/dependencies.md 2 | 3 | module.exports = { 4 | dependency: { 5 | platforms: { 6 | /** 7 | * @type {import('@react-native-community/cli-types').IOSDependencyParams} 8 | */ 9 | ios: {}, 10 | /** 11 | * @type {import('@react-native-community/cli-types').AndroidDependencyParams} 12 | */ 13 | android: {}, 14 | }, 15 | }, 16 | }; 17 | -------------------------------------------------------------------------------- /packages/react-native-nitro-web-image/react-native.config.js: -------------------------------------------------------------------------------- 1 | // https://github.com/react-native-community/cli/blob/main/docs/dependencies.md 2 | 3 | module.exports = { 4 | dependency: { 5 | platforms: { 6 | /** 7 | * @type {import('@react-native-community/cli-types').IOSDependencyParams} 8 | */ 9 | ios: {}, 10 | /** 11 | * @type {import('@react-native-community/cli-types').AndroidDependencyParams} 12 | */ 13 | android: {}, 14 | }, 15 | }, 16 | }; 17 | -------------------------------------------------------------------------------- /example/Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # You may use http://rbenv.org/ or https://rvm.io/ to install and use this version 4 | ruby ">= 2.6.10" 5 | 6 | # Exclude problematic versions of cocoapods and activesupport that causes build failures. 7 | gem 'cocoapods', '>= 1.13', '!= 1.15.0', '!= 1.15.1' 8 | gem 'activesupport', '>= 6.1.7.5', '!= 7.1.0' 9 | gem 'xcodeproj', '< 1.26.0' 10 | gem 'concurrent-ruby', '< 1.3.4' 11 | 12 | # Ruby 3.4.0 has removed some libraries from the standard library. 13 | gem 'bigdecimal' 14 | gem 'logger' 15 | gem 'benchmark' 16 | gem 'mutex_m' 17 | -------------------------------------------------------------------------------- /packages/react-native-nitro-web-image/nitro.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://nitro.margelo.com/nitro.schema.json", 3 | "cxxNamespace": ["web", "image"], 4 | "ios": { 5 | "iosModuleName": "NitroWebImage" 6 | }, 7 | "android": { 8 | "androidNamespace": ["web", "image"], 9 | "androidCxxLibName": "NitroWebImage" 10 | }, 11 | "autolinking": { 12 | "WebImageFactory": { 13 | "swift": "HybridWebImageFactory", 14 | "kotlin": "HybridWebImageFactory" 15 | } 16 | }, 17 | "ignorePaths": ["**/node_modules"] 18 | } 19 | -------------------------------------------------------------------------------- /packages/react-native-nitro-image/ios/NativeImageView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NativeImageView.swift 3 | // Pods 4 | // 5 | // Created by Marc Rousavy on 25.07.25. 6 | // 7 | 8 | import UIKit 9 | 10 | /** 11 | * A protocol that represents a native image view. 12 | * This can be used to downcast from `HybridImageViewSpec` 13 | * which gives you a concrete `UIImageView`. 14 | * 15 | * If you want to use other Image Views with Image Loaders, 16 | * make sure your native view class conforms to this protocol. 17 | */ 18 | public protocol NativeImageView { 19 | var imageView: UIImageView { get } 20 | } 21 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Lint with Biome 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | workflow_dispatch: 11 | 12 | jobs: 13 | lint: 14 | name: Run Biome Linter 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v4 18 | 19 | - name: Setup Bun 20 | uses: oven-sh/setup-bun@v2 21 | 22 | - name: Install dependencies 23 | run: bun install 24 | 25 | - name: Run Biome 26 | run: bun lint-ci 27 | env: 28 | CI: true 29 | -------------------------------------------------------------------------------- /packages/react-native-nitro-image/android/src/main/java/com/margelo/nitro/image/Bitmap+toByteBuffer.kt: -------------------------------------------------------------------------------- 1 | package com.margelo.nitro.image 2 | 3 | import android.graphics.Bitmap 4 | import java.nio.ByteBuffer 5 | 6 | fun Bitmap.toByteBuffer(): ByteBuffer { 7 | var bitmap = this 8 | if (isGPU) { 9 | // It's a GPU Bitmap - we need to copy it to CPU memory first. 10 | bitmap = bitmap.copy(Bitmap.Config.ARGB_8888, false) 11 | } 12 | 13 | val buffer = ByteBuffer.allocateDirect(bitmap.byteCount) 14 | bitmap.copyPixelsToBuffer(buffer) 15 | buffer.rewind() 16 | return buffer 17 | } 18 | -------------------------------------------------------------------------------- /packages/react-native-nitro-image/android/src/main/java/com/margelo/nitro/image/Bitmap+toMutable.kt: -------------------------------------------------------------------------------- 1 | package com.margelo.nitro.image 2 | 3 | import android.graphics.Bitmap 4 | 5 | fun Bitmap.toMutable(forceCopy: Boolean): Bitmap { 6 | if (isMutable && !forceCopy) { 7 | // It's already Mutable! 8 | return this 9 | } 10 | var config = this.config ?: throw Error("Failed to get Bitmap's format! $this") 11 | if (config == Bitmap.Config.HARDWARE) { 12 | // HARDWARE Bitmaps are not mutable, so we need to change to ARGB 13 | config = Bitmap.Config.ARGB_8888 14 | } 15 | return this.copy(config, true) 16 | } 17 | -------------------------------------------------------------------------------- /packages/react-native-nitro-image/android/src/main/java/com/margelo/nitro/image/CustomImageView.kt: -------------------------------------------------------------------------------- 1 | package com.margelo.nitro.image 2 | 3 | import android.content.Context 4 | import androidx.appcompat.widget.AppCompatImageView 5 | 6 | class CustomImageView(context: Context, 7 | private val visibilityChanged: (Boolean) -> Unit): AppCompatImageView(context) { 8 | override fun onAttachedToWindow() { 9 | super.onAttachedToWindow() 10 | visibilityChanged(true) 11 | } 12 | 13 | override fun onDetachedFromWindow() { 14 | super.onDetachedFromWindow() 15 | visibilityChanged(false) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/react-native-nitro-web-image/android/src/main/java/com/margelo/nitro/web/image/AsyncImagePriority+toCoroutineContext.kt: -------------------------------------------------------------------------------- 1 | package com.margelo.nitro.web.image 2 | 3 | import kotlinx.coroutines.Dispatchers 4 | import kotlinx.coroutines.currentCoroutineContext 5 | import kotlin.coroutines.CoroutineContext 6 | 7 | 8 | fun AsyncImagePriority.toCoroutineContext(): CoroutineContext? { 9 | // TODO: Does this look about right? 10 | return when (this) { 11 | AsyncImagePriority.LOW -> Dispatchers.IO.limitedParallelism(2) 12 | AsyncImagePriority.DEFAULT -> null 13 | AsyncImagePriority.HIGH -> Dispatchers.IO 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /example/android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext { 3 | buildToolsVersion = "36.0.0" 4 | minSdkVersion = 26 5 | compileSdkVersion = 36 6 | targetSdkVersion = 36 7 | ndkVersion = "27.1.12297006" 8 | kotlinVersion = "2.1.20" 9 | } 10 | repositories { 11 | google() 12 | mavenCentral() 13 | } 14 | dependencies { 15 | classpath("com.android.tools.build:gradle") 16 | classpath("com.facebook.react:react-native-gradle-plugin") 17 | classpath("org.jetbrains.kotlin:kotlin-gradle-plugin") 18 | } 19 | } 20 | 21 | apply plugin: "com.facebook.react.rootproject" 22 | -------------------------------------------------------------------------------- /packages/react-native-nitro-image/nitrogen/generated/android/kotlin/com/margelo/nitro/image/ImageFormat.kt: -------------------------------------------------------------------------------- 1 | /// 2 | /// ImageFormat.kt 3 | /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. 4 | /// https://github.com/mrousavy/nitro 5 | /// Copyright © 2025 Marc Rousavy @ Margelo 6 | /// 7 | 8 | package com.margelo.nitro.image 9 | 10 | import androidx.annotation.Keep 11 | import com.facebook.proguard.annotations.DoNotStrip 12 | 13 | /** 14 | * Represents the JavaScript enum/union "ImageFormat". 15 | */ 16 | @DoNotStrip 17 | @Keep 18 | enum class ImageFormat(@DoNotStrip @Keep val value: Int) { 19 | JPG(0), 20 | PNG(1), 21 | HEIC(2); 22 | } 23 | -------------------------------------------------------------------------------- /packages/react-native-nitro-image/nitrogen/generated/android/kotlin/com/margelo/nitro/image/ResizeMode.kt: -------------------------------------------------------------------------------- 1 | /// 2 | /// ResizeMode.kt 3 | /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. 4 | /// https://github.com/mrousavy/nitro 5 | /// Copyright © 2025 Marc Rousavy @ Margelo 6 | /// 7 | 8 | package com.margelo.nitro.image 9 | 10 | import androidx.annotation.Keep 11 | import com.facebook.proguard.annotations.DoNotStrip 12 | 13 | /** 14 | * Represents the JavaScript enum/union "ResizeMode". 15 | */ 16 | @DoNotStrip 17 | @Keep 18 | enum class ResizeMode(@DoNotStrip @Keep val value: Int) { 19 | COVER(0), 20 | CONTAIN(1), 21 | CENTER(2), 22 | STRETCH(3); 23 | } 24 | -------------------------------------------------------------------------------- /packages/react-native-nitro-web-image/nitrogen/generated/android/kotlin/com/margelo/nitro/web/image/AsyncImagePriority.kt: -------------------------------------------------------------------------------- 1 | /// 2 | /// AsyncImagePriority.kt 3 | /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. 4 | /// https://github.com/mrousavy/nitro 5 | /// Copyright © 2025 Marc Rousavy @ Margelo 6 | /// 7 | 8 | package com.margelo.nitro.web.image 9 | 10 | import androidx.annotation.Keep 11 | import com.facebook.proguard.annotations.DoNotStrip 12 | 13 | /** 14 | * Represents the JavaScript enum/union "AsyncImagePriority". 15 | */ 16 | @DoNotStrip 17 | @Keep 18 | enum class AsyncImagePriority(@DoNotStrip @Keep val value: Int) { 19 | LOW(0), 20 | DEFAULT(1), 21 | HIGH(2); 22 | } 23 | -------------------------------------------------------------------------------- /packages/react-native-nitro-image/nitrogen/generated/ios/swift/Variant__any_HybridImageSpec___any_HybridImageLoaderSpec_.swift: -------------------------------------------------------------------------------- 1 | /// 2 | /// Variant__any_HybridImageSpec___any_HybridImageLoaderSpec_.swift 3 | /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. 4 | /// https://github.com/mrousavy/nitro 5 | /// Copyright © 2025 Marc Rousavy @ Margelo 6 | /// 7 | 8 | 9 | 10 | /** 11 | * An Swift enum with associated values representing a Variant/Union type. 12 | * JS type: `hybrid-object | hybrid-object` 13 | */ 14 | @frozen 15 | public indirect enum Variant__any_HybridImageSpec___any_HybridImageLoaderSpec_ { 16 | case first((any HybridImageSpec)) 17 | case second((any HybridImageLoaderSpec)) 18 | } 19 | -------------------------------------------------------------------------------- /packages/react-native-nitro-image/src/specs/ImageLoaderFactory.nitro.ts: -------------------------------------------------------------------------------- 1 | import type { HybridObject } from "react-native-nitro-modules"; 2 | import type { EncodedImageData, RawPixelData } from "./Image.nitro"; 3 | import type { ImageLoader } from "./ImageLoader.nitro"; 4 | 5 | export interface ImageLoaderFactory 6 | extends HybridObject<{ ios: "swift"; android: "kotlin" }> { 7 | createFileImageLoader(filePath: string): ImageLoader; 8 | createResourceImageLoader(name: string): ImageLoader; 9 | createSymbolImageLoader(symbolName: string): ImageLoader; 10 | createRawPixelDataImageLoader(data: RawPixelData): ImageLoader; 11 | createEncodedImageDataImageLoader(data: EncodedImageData): ImageLoader; 12 | } 13 | -------------------------------------------------------------------------------- /packages/react-native-nitro-image/ios/UIImage+memorySize.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIImage+memorySize.swift 3 | // react-native-nitro-image 4 | // 5 | // Created by Marc Rousavy on 11.06.25. 6 | // 7 | 8 | import UIKit 9 | 10 | extension UIImage { 11 | private var rgbaMemorySize: Int { 12 | let pixelWidth = Int(size.width * scale) 13 | let pixelHeight = Int(size.height * scale) 14 | let bytesPerPixel = 4 15 | 16 | return pixelWidth * pixelHeight * bytesPerPixel 17 | } 18 | 19 | private var cgImageMemorySize: Int? { 20 | guard let cgImage = cgImage else { return nil } 21 | return cgImage.bytesPerRow * cgImage.height 22 | } 23 | 24 | var memorySize: Int { 25 | return cgImageMemorySize ?? rgbaMemorySize 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /packages/react-native-nitro-image/nitrogen/generated/android/kotlin/com/margelo/nitro/image/PixelFormat.kt: -------------------------------------------------------------------------------- 1 | /// 2 | /// PixelFormat.kt 3 | /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. 4 | /// https://github.com/mrousavy/nitro 5 | /// Copyright © 2025 Marc Rousavy @ Margelo 6 | /// 7 | 8 | package com.margelo.nitro.image 9 | 10 | import androidx.annotation.Keep 11 | import com.facebook.proguard.annotations.DoNotStrip 12 | 13 | /** 14 | * Represents the JavaScript enum/union "PixelFormat". 15 | */ 16 | @DoNotStrip 17 | @Keep 18 | enum class PixelFormat(@DoNotStrip @Keep val value: Int) { 19 | ARGB(0), 20 | BGRA(1), 21 | ABGR(2), 22 | RGBA(3), 23 | XRGB(4), 24 | BGRX(5), 25 | XBGR(6), 26 | RGBX(7), 27 | RGB(8), 28 | BGR(9), 29 | UNKNOWN(10); 30 | } 31 | -------------------------------------------------------------------------------- /example/metro.config.js: -------------------------------------------------------------------------------- 1 | const { getDefaultConfig, mergeConfig } = require("@react-native/metro-config"); 2 | const path = require("node:path"); 3 | 4 | const root = path.resolve(__dirname, ".."); 5 | 6 | /** 7 | * Metro configuration 8 | * https://facebook.github.io/metro/docs/configuration 9 | * 10 | * @type {import('@react-native/metro-config').MetroConfig} 11 | */ 12 | const config = { 13 | watchFolders: [root], 14 | 15 | transformer: { 16 | getTransformOptions: async () => ({ 17 | transform: { 18 | experimentalImportSupport: false, 19 | inlineRequires: true, 20 | }, 21 | }), 22 | }, 23 | }; 24 | 25 | module.exports = mergeConfig(getDefaultConfig(__dirname), config); 26 | console.log(module.exports); 27 | -------------------------------------------------------------------------------- /packages/react-native-nitro-image/src/ImageUtils.ts: -------------------------------------------------------------------------------- 1 | import { NitroModules } from "react-native-nitro-modules"; 2 | import type { ImageUtils } from "./specs/ImageUtils.nitro"; 3 | 4 | const utils = NitroModules.createHybridObject("ImageUtils"); 5 | 6 | /** 7 | * Returns `true` when the host platform supports loading Images 8 | * in `HEIC` format. 9 | */ 10 | export const supportsHeicLoading = utils.supportsHeicLoading; 11 | /** 12 | * Returns `true` when the host platform supports writing Images 13 | * in `HEIC` format. 14 | */ 15 | export const supportsHeicWriting = utils.supportsHeicWriting; 16 | 17 | export const thumbHashToBase64String = 18 | utils.thumbHashToBase64String.bind(utils); 19 | 20 | export const thumbHashFromBase64String = 21 | utils.thumbhashFromBase64String.bind(utils); 22 | -------------------------------------------------------------------------------- /packages/react-native-nitro-image/src/NativeNitroImage.tsx: -------------------------------------------------------------------------------- 1 | import { getHostComponent } from "react-native-nitro-modules"; 2 | import ViewConfig from "../nitrogen/generated/shared/json/NitroImageViewConfig.json"; 3 | import type { 4 | NativeNitroImageViewMethods, 5 | NativeNitroImageViewProps, 6 | } from "./specs/ImageView.nitro"; 7 | 8 | /** 9 | * The native renderable `` view. 10 | * @example 11 | * ```tsx 12 | * function App() { 13 | * const image = useImage('https://picsum.photos/seed/123/400') 14 | * return 15 | * } 16 | * ``` 17 | */ 18 | export const NativeNitroImage = getHostComponent< 19 | NativeNitroImageViewProps, 20 | NativeNitroImageViewMethods 21 | >("NitroImageView", () => ViewConfig); 22 | -------------------------------------------------------------------------------- /example/src/App.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * Sample React Native App 3 | * https://github.com/facebook/react-native 4 | * 5 | * @format 6 | */ 7 | 8 | import { createBottomTabNavigator } from "@react-navigation/bottom-tabs"; 9 | import { createStaticNavigation } from "@react-navigation/native"; 10 | import { EmptyTab } from "./EmptyTab"; 11 | import { FastImageTab } from "./FastImageTab"; 12 | import { NitroImageTab } from "./NitroImageTab"; 13 | 14 | const Tabs = createBottomTabNavigator({ 15 | detachInactiveScreens: false, 16 | screens: { 17 | Empty: EmptyTab, 18 | FastImage: FastImageTab, 19 | NitroImage: NitroImageTab, 20 | }, 21 | }); 22 | const Navigation = createStaticNavigation(Tabs); 23 | 24 | function App(): React.JSX.Element { 25 | return ; 26 | } 27 | 28 | export default App; 29 | -------------------------------------------------------------------------------- /packages/react-native-nitro-image/ios/UIImage+toEncodedImageData.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIImage+toEncodedImageData.swift 3 | // react-native-nitro-image 4 | // 5 | // Created by Marc Rousavy on 22.10.25. 6 | // 7 | 8 | import UIKit 9 | import CoreGraphics 10 | import NitroModules 11 | 12 | extension UIImage { 13 | /** 14 | * Returns encoded Image data of this Image (JPG, PNG, ...) 15 | */ 16 | func toEncodedImageData(format: ImageFormat, quality: Double = 1.0) throws -> EncodedImageData { 17 | let data = try getData(in: format, quality: quality) 18 | let arrayBuffer = try ArrayBuffer.copy(data: data) 19 | return EncodedImageData(buffer: arrayBuffer, 20 | width: self.size.width, 21 | height: self.size.height, 22 | imageFormat: format) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /packages/react-native-nitro-image/nitrogen/generated/android/NitroImageOnLoad.hpp: -------------------------------------------------------------------------------- 1 | /// 2 | /// NitroImageOnLoad.hpp 3 | /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. 4 | /// https://github.com/mrousavy/nitro 5 | /// Copyright © 2025 Marc Rousavy @ Margelo 6 | /// 7 | 8 | #include 9 | #include 10 | 11 | namespace margelo::nitro::image { 12 | 13 | /** 14 | * Initializes the native (C++) part of NitroImage, and autolinks all Hybrid Objects. 15 | * Call this in your `JNI_OnLoad` function (probably inside `cpp-adapter.cpp`). 16 | * Example: 17 | * ```cpp (cpp-adapter.cpp) 18 | * JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void*) { 19 | * return margelo::nitro::image::initialize(vm); 20 | * } 21 | * ``` 22 | */ 23 | int initialize(JavaVM* vm); 24 | 25 | } // namespace margelo::nitro::image 26 | -------------------------------------------------------------------------------- /packages/react-native-nitro-image/nitrogen/generated/android/NitroImage+autolinking.gradle: -------------------------------------------------------------------------------- 1 | /// 2 | /// NitroImage+autolinking.gradle 3 | /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. 4 | /// https://github.com/mrousavy/nitro 5 | /// Copyright © 2025 Marc Rousavy @ Margelo 6 | /// 7 | 8 | /// This is a Gradle file that adds all files generated by Nitrogen 9 | /// to the current Gradle project. 10 | /// 11 | /// To use it, add this to your build.gradle: 12 | /// ```gradle 13 | /// apply from: '../nitrogen/generated/android/NitroImage+autolinking.gradle' 14 | /// ``` 15 | 16 | logger.warn("[NitroModules] 🔥 NitroImage is boosted by nitro!") 17 | 18 | android { 19 | sourceSets { 20 | main { 21 | java.srcDirs += [ 22 | // Nitrogen files 23 | "${project.projectDir}/../nitrogen/generated/android/kotlin" 24 | ] 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /packages/react-native-nitro-web-image/nitrogen/generated/android/NitroWebImage+autolinking.gradle: -------------------------------------------------------------------------------- 1 | /// 2 | /// NitroWebImage+autolinking.gradle 3 | /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. 4 | /// https://github.com/mrousavy/nitro 5 | /// Copyright © 2025 Marc Rousavy @ Margelo 6 | /// 7 | 8 | /// This is a Gradle file that adds all files generated by Nitrogen 9 | /// to the current Gradle project. 10 | /// 11 | /// To use it, add this to your build.gradle: 12 | /// ```gradle 13 | /// apply from: '../nitrogen/generated/android/NitroWebImage+autolinking.gradle' 14 | /// ``` 15 | 16 | logger.warn("[NitroModules] 🔥 NitroWebImage is boosted by nitro!") 17 | 18 | android { 19 | sourceSets { 20 | main { 21 | java.srcDirs += [ 22 | // Nitrogen files 23 | "${project.projectDir}/../nitrogen/generated/android/kotlin" 24 | ] 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /packages/react-native-nitro-web-image/nitrogen/generated/android/NitroWebImageOnLoad.hpp: -------------------------------------------------------------------------------- 1 | /// 2 | /// NitroWebImageOnLoad.hpp 3 | /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. 4 | /// https://github.com/mrousavy/nitro 5 | /// Copyright © 2025 Marc Rousavy @ Margelo 6 | /// 7 | 8 | #include 9 | #include 10 | 11 | namespace margelo::nitro::web::image { 12 | 13 | /** 14 | * Initializes the native (C++) part of NitroWebImage, and autolinks all Hybrid Objects. 15 | * Call this in your `JNI_OnLoad` function (probably inside `cpp-adapter.cpp`). 16 | * Example: 17 | * ```cpp (cpp-adapter.cpp) 18 | * JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void*) { 19 | * return margelo::nitro::web::image::initialize(vm); 20 | * } 21 | * ``` 22 | */ 23 | int initialize(JavaVM* vm); 24 | 25 | } // namespace margelo::nitro::web::image 26 | -------------------------------------------------------------------------------- /packages/react-native-nitro-image/nitrogen/generated/android/kotlin/com/margelo/nitro/image/views/HybridNitroImageViewStateUpdater.kt: -------------------------------------------------------------------------------- 1 | /// 2 | /// HybridNitroImageViewStateUpdater.kt 3 | /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. 4 | /// https://github.com/mrousavy/nitro 5 | /// Copyright © 2025 Marc Rousavy @ Margelo 6 | /// 7 | 8 | package com.margelo.nitro.image.views 9 | 10 | import com.facebook.react.uimanager.StateWrapper 11 | import com.margelo.nitro.image.* 12 | 13 | internal class HybridNitroImageViewStateUpdater { 14 | companion object { 15 | /** 16 | * Updates the props for [view] through C++. 17 | * The [state] prop is expected to contain [view]'s props as wrapped Fabric state. 18 | */ 19 | @Suppress("KotlinJniMissingFunction") 20 | @JvmStatic 21 | external fun updateViewProps(view: HybridNitroImageViewSpec, state: StateWrapper) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /example/src/FastImageTab.tsx: -------------------------------------------------------------------------------- 1 | import { useMemo } from "react"; 2 | import { FlatList, StyleSheet, Text, View } from "react-native"; 3 | import FastImage from "react-native-fast-image"; 4 | import { createImageURLs } from "./createImageURLs"; 5 | 6 | export function FastImageTab() { 7 | const imageURLs = useMemo(() => createImageURLs(), []); 8 | 9 | return ( 10 | 11 | FastImage Tab 12 | ( 17 | 18 | )} 19 | /> 20 | 21 | ); 22 | } 23 | 24 | const styles = StyleSheet.create({ 25 | image: { 26 | width: "25%", 27 | aspectRatio: 1, 28 | }, 29 | }); 30 | -------------------------------------------------------------------------------- /packages/react-native-nitro-image/src/specs/ImageUtils.nitro.ts: -------------------------------------------------------------------------------- 1 | import type { HybridObject } from "react-native-nitro-modules"; 2 | 3 | export interface ImageUtils 4 | extends HybridObject<{ ios: "swift"; android: "kotlin" }> { 5 | /** 6 | * Returns `true` when the host platform supports loading Images 7 | * in `HEIC` format. 8 | */ 9 | readonly supportsHeicLoading: boolean; 10 | /** 11 | * Returns `true` when the host platform supports writing Images 12 | * in `HEIC` format. 13 | */ 14 | readonly supportsHeicWriting: boolean; 15 | 16 | /** 17 | * Converts the given ThumbHash {@linkcode ArrayBuffer} to a `string`. 18 | */ 19 | thumbHashToBase64String(thumbhash: ArrayBuffer): string; 20 | /** 21 | * Converts the given ThumbHash `string` to an {@linkcode ArrayBuffer}. 22 | */ 23 | thumbhashFromBase64String(thumbhashBase64: string): ArrayBuffer; 24 | } 25 | -------------------------------------------------------------------------------- /packages/react-native-nitro-image/android/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | project(NitroImage) 2 | cmake_minimum_required(VERSION 3.9.0) 3 | 4 | set (PACKAGE_NAME NitroImage) 5 | set (CMAKE_VERBOSE_MAKEFILE ON) 6 | set (CMAKE_CXX_STANDARD 20) 7 | 8 | # Enable Raw Props parsing in react-native (for Nitro Views) 9 | add_compile_options(-DRN_SERIALIZABLE_STATE=1) 10 | 11 | # Define C++ library and add all sources 12 | add_library(${PACKAGE_NAME} SHARED 13 | src/main/cpp/cpp-adapter.cpp 14 | ) 15 | 16 | # Add Nitrogen specs :) 17 | include(${CMAKE_SOURCE_DIR}/../nitrogen/generated/android/NitroImage+autolinking.cmake) 18 | 19 | # Set up local includes 20 | include_directories( 21 | "src/main/cpp" 22 | "../cpp" 23 | ) 24 | 25 | find_library(LOG_LIB log) 26 | 27 | # Link all libraries together 28 | target_link_libraries( 29 | ${PACKAGE_NAME} 30 | ${LOG_LIB} 31 | android # <-- Android core 32 | ) 33 | -------------------------------------------------------------------------------- /packages/react-native-nitro-image/nitrogen/generated/shared/c++/HybridImageLoaderSpec.cpp: -------------------------------------------------------------------------------- 1 | /// 2 | /// HybridImageLoaderSpec.cpp 3 | /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. 4 | /// https://github.com/mrousavy/nitro 5 | /// Copyright © 2025 Marc Rousavy @ Margelo 6 | /// 7 | 8 | #include "HybridImageLoaderSpec.hpp" 9 | 10 | namespace margelo::nitro::image { 11 | 12 | void HybridImageLoaderSpec::loadHybridMethods() { 13 | // load base methods/properties 14 | HybridObject::loadHybridMethods(); 15 | // load custom methods/properties 16 | registerHybrids(this, [](Prototype& prototype) { 17 | prototype.registerHybridMethod("loadImage", &HybridImageLoaderSpec::loadImage); 18 | prototype.registerHybridMethod("requestImage", &HybridImageLoaderSpec::requestImage); 19 | prototype.registerHybridMethod("dropImage", &HybridImageLoaderSpec::dropImage); 20 | }); 21 | } 22 | 23 | } // namespace margelo::nitro::image 24 | -------------------------------------------------------------------------------- /packages/react-native-nitro-image/nitro.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://nitro.margelo.com/nitro.schema.json", 3 | "cxxNamespace": ["image"], 4 | "ios": { 5 | "iosModuleName": "NitroImage" 6 | }, 7 | "android": { 8 | "androidNamespace": ["image"], 9 | "androidCxxLibName": "NitroImage" 10 | }, 11 | "autolinking": { 12 | "ImageFactory": { 13 | "swift": "HybridImageFactory", 14 | "kotlin": "HybridImageFactory" 15 | }, 16 | "ImageLoaderFactory": { 17 | "swift": "HybridImageLoaderFactory", 18 | "kotlin": "HybridImageLoaderFactory" 19 | }, 20 | "ImageUtils": { 21 | "swift": "HybridImageUtils", 22 | "kotlin": "HybridImageUtils" 23 | }, 24 | "NitroImageView": { 25 | "swift": "HybridImageView", 26 | "kotlin": "HybridImageView" 27 | } 28 | }, 29 | "ignorePaths": ["**/node_modules"] 30 | } 31 | -------------------------------------------------------------------------------- /example/ios/NitroImageExample/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images": [ 3 | { 4 | "idiom": "iphone", 5 | "scale": "2x", 6 | "size": "20x20" 7 | }, 8 | { 9 | "idiom": "iphone", 10 | "scale": "3x", 11 | "size": "20x20" 12 | }, 13 | { 14 | "idiom": "iphone", 15 | "scale": "2x", 16 | "size": "29x29" 17 | }, 18 | { 19 | "idiom": "iphone", 20 | "scale": "3x", 21 | "size": "29x29" 22 | }, 23 | { 24 | "idiom": "iphone", 25 | "scale": "2x", 26 | "size": "40x40" 27 | }, 28 | { 29 | "idiom": "iphone", 30 | "scale": "3x", 31 | "size": "40x40" 32 | }, 33 | { 34 | "idiom": "iphone", 35 | "scale": "2x", 36 | "size": "60x60" 37 | }, 38 | { 39 | "idiom": "iphone", 40 | "scale": "3x", 41 | "size": "60x60" 42 | }, 43 | { 44 | "idiom": "ios-marketing", 45 | "scale": "1x", 46 | "size": "1024x1024" 47 | } 48 | ], 49 | "info": { 50 | "author": "xcode", 51 | "version": 1 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /packages/react-native-nitro-web-image/android/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | project(NitroWebImage) 2 | cmake_minimum_required(VERSION 3.9.0) 3 | 4 | set (PACKAGE_NAME NitroWebImage) 5 | set (CMAKE_VERBOSE_MAKEFILE ON) 6 | set (CMAKE_CXX_STANDARD 20) 7 | 8 | # Define C++ library and add all sources 9 | add_library(${PACKAGE_NAME} SHARED 10 | src/main/cpp/cpp-adapter.cpp 11 | ) 12 | 13 | # Add Nitrogen specs :) 14 | include(${CMAKE_SOURCE_DIR}/../nitrogen/generated/android/NitroWebImage+autolinking.cmake) 15 | 16 | # Set up local includes 17 | include_directories( 18 | "src/main/cpp" 19 | "../cpp" 20 | ) 21 | 22 | find_library(LOG_LIB log) 23 | find_package(react-native-nitro-image REQUIRED) # <-- for the HybridImage type 24 | 25 | # Link all libraries together 26 | target_link_libraries( 27 | ${PACKAGE_NAME} 28 | ${LOG_LIB} 29 | android # <-- Android core 30 | react-native-nitro-image::NitroImage # <-- NitroImage 31 | ) 32 | -------------------------------------------------------------------------------- /example/src/NitroImageTab.tsx: -------------------------------------------------------------------------------- 1 | import { useMemo } from "react"; 2 | import { FlatList, StyleSheet, Text, View } from "react-native"; 3 | import { NitroImage } from "react-native-nitro-image"; 4 | import { createImageURLs } from "./createImageURLs"; 5 | 6 | export function NitroImageTab() { 7 | const imageURLs = useMemo(() => createImageURLs(), []); 8 | 9 | return ( 10 | 11 | NitroImage Tab 12 | ( 17 | 21 | )} 22 | /> 23 | 24 | ); 25 | } 26 | 27 | const styles= StyleSheet.create({ 28 | image: { 29 | width: '25%', 30 | aspectRatio: 1 31 | } 32 | }) 33 | -------------------------------------------------------------------------------- /packages/react-native-nitro-image/android/src/main/java/com/margelo/nitro/image/HybridImageLoader.kt: -------------------------------------------------------------------------------- 1 | package com.margelo.nitro.image 2 | 3 | import androidx.annotation.Keep 4 | import com.facebook.proguard.annotations.DoNotStrip 5 | import com.margelo.nitro.core.Promise 6 | 7 | @Keep 8 | @DoNotStrip 9 | class HybridImageLoader(private val loadImageFunc: () -> Promise): HybridImageLoaderSpec() { 10 | override fun loadImage(): Promise = loadImageFunc() 11 | 12 | override fun requestImage(forView: HybridNitroImageViewSpec) { 13 | val view = forView as? HybridImageView ?: return 14 | 15 | loadImage().then { maybeImage -> 16 | val image = maybeImage as? HybridImage ?: return@then 17 | view.imageView.setImageBitmap(image.bitmap) 18 | } 19 | } 20 | 21 | override fun dropImage(forView: HybridNitroImageViewSpec) { 22 | val view = forView as? HybridImageView ?: return 23 | view.imageView.setImageDrawable(null) 24 | } 25 | } -------------------------------------------------------------------------------- /packages/react-native-nitro-image/src/useImageLoader.ts: -------------------------------------------------------------------------------- 1 | import { useMemo } from "react"; 2 | import { type AsyncImageSource, isHybridObject } from "./AsyncImageSource"; 3 | import { createImageLoader } from "./createImageLoader"; 4 | import { markHybridObject } from "./markHybridObject"; 5 | import type { Image } from "./specs/Image.nitro"; 6 | import type { ImageLoader } from "./specs/ImageLoader.nitro"; 7 | 8 | export function useImageLoader( 9 | source: AsyncImageSource, 10 | ): Image | ImageLoader | undefined { 11 | // biome-ignore lint: The dependencies array is a bit hacky. 12 | return useMemo(() => { 13 | // 1. Create the Image/ImageLoader instance 14 | const loader = createImageLoader(source); 15 | // 2. Add `__source` as a property on the JS side so React diffs properly 16 | markHybridObject(loader, source); 17 | // 3. Return it 18 | return loader; 19 | }, [isHybridObject(source) ? source : JSON.stringify(source)]); 20 | } 21 | -------------------------------------------------------------------------------- /packages/react-native-nitro-web-image/nitrogen/generated/shared/c++/HybridWebImageFactorySpec.cpp: -------------------------------------------------------------------------------- 1 | /// 2 | /// HybridWebImageFactorySpec.cpp 3 | /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. 4 | /// https://github.com/mrousavy/nitro 5 | /// Copyright © 2025 Marc Rousavy @ Margelo 6 | /// 7 | 8 | #include "HybridWebImageFactorySpec.hpp" 9 | 10 | namespace margelo::nitro::web::image { 11 | 12 | void HybridWebImageFactorySpec::loadHybridMethods() { 13 | // load base methods/properties 14 | HybridObject::loadHybridMethods(); 15 | // load custom methods/properties 16 | registerHybrids(this, [](Prototype& prototype) { 17 | prototype.registerHybridMethod("createWebImageLoader", &HybridWebImageFactorySpec::createWebImageLoader); 18 | prototype.registerHybridMethod("loadFromURLAsync", &HybridWebImageFactorySpec::loadFromURLAsync); 19 | prototype.registerHybridMethod("preload", &HybridWebImageFactorySpec::preload); 20 | }); 21 | } 22 | 23 | } // namespace margelo::nitro::web::image 24 | -------------------------------------------------------------------------------- /config/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "exclude": [ 3 | "**/node_modules", 4 | "**/lib", 5 | "**/.eslintrc.js", 6 | "**/.prettierrc.js", 7 | "**/jest.config.js", 8 | "**/babel.config.js", 9 | "**/metro.config.js", 10 | "**/tsconfig.json" 11 | ], 12 | "compilerOptions": { 13 | "composite": true, 14 | "allowUnreachableCode": false, 15 | "allowUnusedLabels": false, 16 | "esModuleInterop": true, 17 | "forceConsistentCasingInFileNames": true, 18 | "lib": ["ESNext"], 19 | "module": "ESNext", 20 | "moduleResolution": "Node", 21 | "noEmit": false, 22 | "noFallthroughCasesInSwitch": true, 23 | "noImplicitReturns": true, 24 | "noImplicitUseStrict": false, 25 | "noStrictGenericChecks": false, 26 | "noUncheckedIndexedAccess": true, 27 | "noUnusedLocals": true, 28 | "noUnusedParameters": true, 29 | "resolveJsonModule": true, 30 | "skipLibCheck": true, 31 | "strict": true, 32 | "target": "esnext", 33 | "verbatimModuleSyntax": true 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /packages/react-native-nitro-image/ios/UIImageOrientation+fromDegrees.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIImageOrientation+fromDegrees.swift 3 | // react-native-nitro-image 4 | // 5 | // Created by Marc Rousavy on 11.06.25. 6 | // 7 | 8 | import UIKit 9 | import NitroModules 10 | 11 | extension UIImage.Orientation { 12 | private func rotated90CW() -> UIImage.Orientation { 13 | switch self { 14 | case .up: return .right 15 | case .right: return .down 16 | case .down: return .left 17 | case .left: return .up 18 | case .upMirrored: return .rightMirrored 19 | case .rightMirrored: return .downMirrored 20 | case .downMirrored: return .leftMirrored 21 | case .leftMirrored: return .upMirrored 22 | @unknown default: return .right 23 | } 24 | } 25 | func rotated(byRightAngles k: Int) -> UIImage.Orientation { 26 | let t = ((k % 4) + 4) % 4 27 | var o = self 28 | for _ in 0.. min_ios_version_supported, :visionos => 1.0 } 14 | s.source = { :git => "https://github.com/mrousavy/nitro.git", :tag => "#{s.version}" } 15 | 16 | s.source_files = [ 17 | # Implementation (Swift) 18 | "ios/**/*.{swift}", 19 | # Autolinking/Registration (Objective-C++) 20 | "ios/**/*.{m,mm}", 21 | # Implementation (C++ objects) 22 | "cpp/**/*.{hpp,cpp}", 23 | ] 24 | 25 | load 'nitrogen/generated/ios/NitroImage+autolinking.rb' 26 | add_nitrogen_files(s) 27 | 28 | s.dependency 'React-jsi' 29 | s.dependency 'React-callinvoker' 30 | install_modules_dependencies(s) 31 | end 32 | -------------------------------------------------------------------------------- /packages/react-native-nitro-image/android/src/main/java/com/margelo/nitro/image/Bitmap+compressInMemory.kt: -------------------------------------------------------------------------------- 1 | package com.margelo.nitro.image 2 | 3 | import android.graphics.Bitmap 4 | import java.nio.ByteBuffer 5 | 6 | fun Bitmap.compressInMemory(format: ImageFormat, quality: Int): ByteBuffer { 7 | if (quality < 0 || quality > 100) { 8 | throw Error("Image quality has to be between 0 and 100! (Received: $quality)") 9 | } 10 | val estimatedByteSize = when (format) { 11 | ImageFormat.JPG -> (width * height) / 2 12 | ImageFormat.PNG -> width * height 13 | ImageFormat.HEIC -> width * height 14 | } 15 | 16 | FastByteArrayOutputStream(estimatedByteSize).use { out -> 17 | val successful = this.compress(format.toBitmapFormat(), quality, out) 18 | if (!successful) { 19 | throw Error("Failed to compress the Bitmap into EncodedImageData! (Format: ${format.name}, " + 20 | "Quality: ${quality}, Written Bytes: ${out.count})") 21 | } 22 | return out.toByteBuffer() 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # OSX 2 | # 3 | .DS_Store 4 | 5 | **/.xcode.env.local 6 | 7 | # XDE 8 | .expo/ 9 | 10 | # VSCode 11 | .vscode/ 12 | jsconfig.json 13 | 14 | # Xcode 15 | # 16 | build/ 17 | *.pbxuser 18 | !default.pbxuser 19 | *.mode1v3 20 | !default.mode1v3 21 | *.mode2v3 22 | !default.mode2v3 23 | *.perspectivev3 24 | !default.perspectivev3 25 | xcuserdata 26 | *.xccheckout 27 | *.moved-aside 28 | DerivedData 29 | *.hmap 30 | *.ipa 31 | *.xcuserstate 32 | project.xcworkspace 33 | 34 | # Android/IJ 35 | # 36 | .classpath 37 | .cxx 38 | .gradle 39 | .idea 40 | .project 41 | .settings 42 | local.properties 43 | android.iml 44 | 45 | # Cocoapods 46 | # 47 | example/ios/Pods 48 | 49 | # Ruby 50 | example/vendor/ 51 | 52 | # node.js 53 | # 54 | node_modules/ 55 | npm-debug.log 56 | 57 | # Bun 58 | package-lock.json 59 | **/*.bun 60 | 61 | # BUCK 62 | buck-out/ 63 | \.buckd/ 64 | android/app/libs 65 | android/keystores/debug.keystore 66 | 67 | # Expo 68 | .expo/ 69 | 70 | # Turborepo 71 | .turbo/ 72 | 73 | # generated by bob 74 | lib/ 75 | 76 | # TypeScript 77 | tsconfig.tsbuildinfo 78 | 79 | -------------------------------------------------------------------------------- /.github/workflows/run-nitrogen.yml: -------------------------------------------------------------------------------- 1 | name: Run Nitrogen 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | paths: 8 | - '.github/workflows/run-nitrogen.yml' 9 | - 'src/specs/**' 10 | - '**/nitro.json' 11 | - '**/package.json' 12 | pull_request: 13 | paths: 14 | - '.github/workflows/run-nitrogen.yml' 15 | - 'src/specs/**' 16 | - '**/nitro.json' 17 | - '**/package.json' 18 | 19 | jobs: 20 | lint: 21 | name: Run Nitrogen 22 | runs-on: ubuntu-latest 23 | steps: 24 | - uses: actions/checkout@v4 25 | - uses: oven-sh/setup-bun@v2 26 | 27 | - name: Install npm dependencies (bun) 28 | run: bun install 29 | 30 | - name: Build the typescript types 31 | run: bun run build 32 | 33 | - name: Run nitrogen for NitroImage 34 | run: bun image specs 35 | 36 | - name: Run nitrogen for NitroWebImage 37 | run: bun web-image specs 38 | 39 | - name: Verify no files have changed after nitrogen 40 | run: git diff --exit-code HEAD -- . ':(exclude)bun.lockb' 41 | -------------------------------------------------------------------------------- /packages/react-native-nitro-image/nitrogen/generated/android/kotlin/com/margelo/nitro/image/Color.kt: -------------------------------------------------------------------------------- 1 | /// 2 | /// Color.kt 3 | /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. 4 | /// https://github.com/mrousavy/nitro 5 | /// Copyright © 2025 Marc Rousavy @ Margelo 6 | /// 7 | 8 | package com.margelo.nitro.image 9 | 10 | import androidx.annotation.Keep 11 | import com.facebook.proguard.annotations.DoNotStrip 12 | 13 | 14 | /** 15 | * Represents the JavaScript object/struct "Color". 16 | */ 17 | @DoNotStrip 18 | @Keep 19 | data class Color( 20 | @DoNotStrip 21 | @Keep 22 | val r: Double, 23 | @DoNotStrip 24 | @Keep 25 | val g: Double, 26 | @DoNotStrip 27 | @Keep 28 | val b: Double, 29 | @DoNotStrip 30 | @Keep 31 | val a: Double? 32 | ) { 33 | private companion object { 34 | /** 35 | * Constructor called from C++ 36 | */ 37 | @DoNotStrip 38 | @Keep 39 | @Suppress("unused") 40 | @JvmStatic 41 | private fun fromCpp(r: Double, g: Double, b: Double, a: Double?): Color { 42 | return Color(r, g, b, a) 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /packages/react-native-nitro-web-image/nitrogen/generated/ios/NitroWebImageAutolinking.mm: -------------------------------------------------------------------------------- 1 | /// 2 | /// NitroWebImageAutolinking.mm 3 | /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. 4 | /// https://github.com/mrousavy/nitro 5 | /// Copyright © 2025 Marc Rousavy @ Margelo 6 | /// 7 | 8 | #import 9 | #import 10 | #import "NitroWebImage-Swift-Cxx-Umbrella.hpp" 11 | #import 12 | 13 | #include "HybridWebImageFactorySpecSwift.hpp" 14 | 15 | @interface NitroWebImageAutolinking : NSObject 16 | @end 17 | 18 | @implementation NitroWebImageAutolinking 19 | 20 | + (void) load { 21 | using namespace margelo::nitro; 22 | using namespace margelo::nitro::web::image; 23 | 24 | HybridObjectRegistry::registerHybridObjectConstructor( 25 | "WebImageFactory", 26 | []() -> std::shared_ptr { 27 | std::shared_ptr hybridObject = NitroWebImage::NitroWebImageAutolinking::createWebImageFactory(); 28 | return hybridObject; 29 | } 30 | ); 31 | } 32 | 33 | @end 34 | -------------------------------------------------------------------------------- /packages/react-native-nitro-image/android/src/main/java/com/margelo/nitro/image/ArrayBuffer+toByteArray.kt: -------------------------------------------------------------------------------- 1 | package com.margelo.nitro.image 2 | 3 | import com.margelo.nitro.core.ArrayBuffer 4 | import java.nio.ByteBuffer 5 | 6 | fun ArrayBuffer.toByteArray(): ByteArray { 7 | val buffer = this.getBuffer(false) 8 | if (buffer.hasArray()) { 9 | // It's a CPU-backed array - we can return this directly 10 | val array = buffer.array() 11 | if (array.size == this.size) { 12 | // The CPU-backed array is exactly the view we have in our ArrayBuffer. 13 | // Return as is! 14 | return array 15 | } 16 | // we had a CPU-backed array, but it's size differs from our ArrayBuffer size. 17 | // This might be because the ArrayBuffer has a smaller view of the data, so we need 18 | // to resort back to a good ol' copy. 19 | } 20 | // It's not a CPU-backed array (e.g. HardwareBuffer) - we need to copy to the CPU 21 | val copy = ByteBuffer.allocate(buffer.capacity()) 22 | copy.put(buffer) 23 | return copy.array() 24 | } 25 | -------------------------------------------------------------------------------- /example/src/EmptyTab.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import { StyleSheet, TextInput, View } from "react-native"; 3 | import { NitroImage } from "react-native-nitro-image"; 4 | 5 | export function EmptyTab() { 6 | const [value, setValue] = useState('https://picsum.photos/seed/123/600') 7 | 8 | return ( 9 | 10 | 11 | 12 | 13 | ); 14 | } 15 | 16 | const styles = StyleSheet.create({ 17 | container: { 18 | flex: 1, 19 | justifyContent: "center", 20 | alignItems: "center", 21 | }, 22 | text: { 23 | fontSize: 18, 24 | fontWeight: "500", 25 | }, 26 | textInput: { 27 | borderWidth: 1, 28 | borderRadius: 5, 29 | paddingHorizontal: 10, 30 | }, 31 | image: { 32 | width: 350, 33 | height: 350, 34 | backgroundColor: 'grey', 35 | marginTop: 15 36 | } 37 | }); 38 | -------------------------------------------------------------------------------- /packages/react-native-nitro-image/src/specs/ImageLoader.nitro.ts: -------------------------------------------------------------------------------- 1 | import type { HybridObject } from "react-native-nitro-modules"; 2 | import type { Image } from "./Image.nitro"; 3 | import type { NitroImageView } from "./ImageView.nitro"; 4 | 5 | export interface ImageLoader 6 | extends HybridObject<{ ios: "swift"; android: "kotlin" }> { 7 | /** 8 | * Imperatively loads this `Image` using the underlying load implementation. 9 | */ 10 | loadImage(): Promise; 11 | 12 | /** 13 | * Called by an Image View when it becomes visible. 14 | * The native implementation must set the `image` on the `imageView` by downcasting it to a concrete type. 15 | * @param forView The native view type (e.g. `HybridImageView`) 16 | */ 17 | requestImage(forView: NitroImageView): void; 18 | /** 19 | * Called by an Image View when it becomes invisible. 20 | * The native implementation can remove the `image` on the `imageView` if needed to save memory. 21 | * @param forView The native view type (e.g. `HybridImageView`) 22 | */ 23 | dropImage(forView: NitroImageView): void; 24 | } 25 | -------------------------------------------------------------------------------- /packages/react-native-nitro-image/android/src/main/java/com/margelo/nitro/image/FastByteArrayOutputStream.kt: -------------------------------------------------------------------------------- 1 | package com.margelo.nitro.image 2 | 3 | import java.io.OutputStream 4 | import java.nio.ByteBuffer 5 | 6 | class FastByteArrayOutputStream(initialSize: Int = 64 * 1024) : OutputStream() { 7 | var bytes = ByteArray(initialSize) 8 | private set 9 | var count = 0 10 | private set 11 | 12 | override fun write(b: Int) { 13 | val i = count + 1 14 | ensureCapacity(i) 15 | bytes[count] = b.toByte() 16 | count = i 17 | } 18 | 19 | override fun write(b: ByteArray, off: Int, len: Int) { 20 | val i = count + len 21 | ensureCapacity(i) 22 | System.arraycopy(b, off, bytes, count, len) 23 | count = i 24 | } 25 | 26 | private fun ensureCapacity(min: Int) { 27 | if (min <= bytes.size) return 28 | var newCap = bytes.size.coerceAtLeast(1) 29 | while (newCap < min) newCap = newCap shl 1 30 | bytes = bytes.copyOf(newCap) 31 | } 32 | 33 | fun toByteBuffer(): ByteBuffer = ByteBuffer.wrap(bytes, 0, count) 34 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Marc Rousavy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /example/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 13 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /packages/react-native-nitro-image/nitrogen/generated/shared/c++/HybridImageUtilsSpec.cpp: -------------------------------------------------------------------------------- 1 | /// 2 | /// HybridImageUtilsSpec.cpp 3 | /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. 4 | /// https://github.com/mrousavy/nitro 5 | /// Copyright © 2025 Marc Rousavy @ Margelo 6 | /// 7 | 8 | #include "HybridImageUtilsSpec.hpp" 9 | 10 | namespace margelo::nitro::image { 11 | 12 | void HybridImageUtilsSpec::loadHybridMethods() { 13 | // load base methods/properties 14 | HybridObject::loadHybridMethods(); 15 | // load custom methods/properties 16 | registerHybrids(this, [](Prototype& prototype) { 17 | prototype.registerHybridGetter("supportsHeicLoading", &HybridImageUtilsSpec::getSupportsHeicLoading); 18 | prototype.registerHybridGetter("supportsHeicWriting", &HybridImageUtilsSpec::getSupportsHeicWriting); 19 | prototype.registerHybridMethod("thumbHashToBase64String", &HybridImageUtilsSpec::thumbHashToBase64String); 20 | prototype.registerHybridMethod("thumbhashFromBase64String", &HybridImageUtilsSpec::thumbhashFromBase64String); 21 | }); 22 | } 23 | 24 | } // namespace margelo::nitro::image 25 | -------------------------------------------------------------------------------- /packages/react-native-nitro-web-image/NitroWebImage.podspec: -------------------------------------------------------------------------------- 1 | require "json" 2 | 3 | package = JSON.parse(File.read(File.join(__dir__, "package.json"))) 4 | 5 | Pod::Spec.new do |s| 6 | s.name = "NitroWebImage" 7 | s.version = package["version"] 8 | s.summary = package["description"] 9 | s.homepage = package["homepage"] 10 | s.license = package["license"] 11 | s.authors = package["author"] 12 | 13 | s.platforms = { :ios => min_ios_version_supported, :visionos => 1.0 } 14 | s.source = { :git => "https://github.com/mrousavy/nitro.git", :tag => "#{s.version}" } 15 | 16 | s.source_files = [ 17 | # Implementation (Swift) 18 | "ios/**/*.{swift}", 19 | # Autolinking/Registration (Objective-C++) 20 | "ios/**/*.{m,mm}", 21 | # Implementation (C++ objects) 22 | "cpp/**/*.{hpp,cpp}", 23 | ] 24 | 25 | load 'nitrogen/generated/ios/NitroWebImage+autolinking.rb' 26 | add_nitrogen_files(s) 27 | 28 | s.dependency 'React-jsi' 29 | s.dependency 'React-callinvoker' 30 | s.dependency 'SDWebImage' 31 | s.dependency 'NitroImage' 32 | install_modules_dependencies(s) 33 | end 34 | -------------------------------------------------------------------------------- /example/ios/NitroImageExample/PrivacyInfo.xcprivacy: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSPrivacyAccessedAPITypes 6 | 7 | 8 | NSPrivacyAccessedAPIType 9 | NSPrivacyAccessedAPICategoryFileTimestamp 10 | NSPrivacyAccessedAPITypeReasons 11 | 12 | C617.1 13 | 14 | 15 | 16 | NSPrivacyAccessedAPIType 17 | NSPrivacyAccessedAPICategoryUserDefaults 18 | NSPrivacyAccessedAPITypeReasons 19 | 20 | CA92.1 21 | 22 | 23 | 24 | NSPrivacyAccessedAPIType 25 | NSPrivacyAccessedAPICategorySystemBootTime 26 | NSPrivacyAccessedAPITypeReasons 27 | 28 | 35F9.1 29 | 30 | 31 | 32 | NSPrivacyCollectedDataTypes 33 | 34 | NSPrivacyTracking 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /packages/react-native-nitro-web-image/nitrogen/generated/ios/NitroWebImageAutolinking.swift: -------------------------------------------------------------------------------- 1 | /// 2 | /// NitroWebImageAutolinking.swift 3 | /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. 4 | /// https://github.com/mrousavy/nitro 5 | /// Copyright © 2025 Marc Rousavy @ Margelo 6 | /// 7 | 8 | public final class NitroWebImageAutolinking { 9 | public typealias bridge = margelo.nitro.web.image.bridge.swift 10 | 11 | /** 12 | * Creates an instance of a Swift class that implements `HybridWebImageFactorySpec`, 13 | * and wraps it in a Swift class that can directly interop with C++ (`HybridWebImageFactorySpec_cxx`) 14 | * 15 | * This is generated by Nitrogen and will initialize the class specified 16 | * in the `"autolinking"` property of `nitro.json` (in this case, `HybridWebImageFactory`). 17 | */ 18 | public static func createWebImageFactory() -> bridge.std__shared_ptr_HybridWebImageFactorySpec_ { 19 | let hybridObject = HybridWebImageFactory() 20 | return { () -> bridge.std__shared_ptr_HybridWebImageFactorySpec_ in 21 | let __cxxWrapped = hybridObject.getCxxWrapper() 22 | return __cxxWrapped.getCxxPart() 23 | }() 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /packages/react-native-nitro-image/nitrogen/generated/ios/swift/ImageFormat.swift: -------------------------------------------------------------------------------- 1 | /// 2 | /// ImageFormat.swift 3 | /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. 4 | /// https://github.com/mrousavy/nitro 5 | /// Copyright © 2025 Marc Rousavy @ Margelo 6 | /// 7 | 8 | /** 9 | * Represents the JS union `ImageFormat`, backed by a C++ enum. 10 | */ 11 | public typealias ImageFormat = margelo.nitro.image.ImageFormat 12 | 13 | public extension ImageFormat { 14 | /** 15 | * Get a ImageFormat for the given String value, or 16 | * return `nil` if the given value was invalid/unknown. 17 | */ 18 | init?(fromString string: String) { 19 | switch string { 20 | case "jpg": 21 | self = .jpg 22 | case "png": 23 | self = .png 24 | case "heic": 25 | self = .heic 26 | default: 27 | return nil 28 | } 29 | } 30 | 31 | /** 32 | * Get the String value this ImageFormat represents. 33 | */ 34 | var stringValue: String { 35 | switch self { 36 | case .jpg: 37 | return "jpg" 38 | case .png: 39 | return "png" 40 | case .heic: 41 | return "heic" 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /example/android/app/src/main/java/com/nitroimageexample/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.nitroimageexample 2 | 3 | import com.facebook.react.ReactActivity 4 | import com.facebook.react.ReactActivityDelegate 5 | import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.fabricEnabled 6 | import com.facebook.react.defaults.DefaultReactActivityDelegate 7 | import android.os.Bundle 8 | 9 | class MainActivity : ReactActivity() { 10 | 11 | /** 12 | * Returns the name of the main component registered from JavaScript. This is used to schedule 13 | * rendering of the component. 14 | */ 15 | override fun getMainComponentName(): String = "NitroImageExample" 16 | 17 | /** 18 | * Returns the instance of the [ReactActivityDelegate]. We use [DefaultReactActivityDelegate] 19 | * which allows you to enable New Architecture with a single boolean flags [fabricEnabled] 20 | */ 21 | override fun createReactActivityDelegate(): ReactActivityDelegate = 22 | DefaultReactActivityDelegate(this, mainComponentName, fabricEnabled) 23 | 24 | /** 25 | * For react-navigation 26 | */ 27 | override fun onCreate(savedInstanceState: Bundle?) { 28 | super.onCreate(null) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /example/ios/Podfile: -------------------------------------------------------------------------------- 1 | # Resolve react_native_pods.rb with node to allow for hoisting 2 | require Pod::Executable.execute_command('node', ['-p', 3 | 'require.resolve( 4 | "react-native/scripts/react_native_pods.rb", 5 | {paths: [process.argv[1]]}, 6 | )', __dir__]).strip 7 | 8 | platform :ios, min_ios_version_supported 9 | prepare_react_native_project! 10 | 11 | linkage = ENV['USE_FRAMEWORKS'] 12 | if linkage != nil 13 | Pod::UI.puts "Configuring Pod with #{linkage}ally linked Frameworks".green 14 | use_frameworks! :linkage => linkage.to_sym 15 | end 16 | 17 | target 'NitroImageExample' do 18 | config = use_native_modules! 19 | pod 'SDWebImage', :modular_headers => true 20 | 21 | use_react_native!( 22 | :path => config[:reactNativePath], 23 | # An absolute path to your application root. 24 | :app_path => "#{Pod::Config.instance.installation_root}/.." 25 | ) 26 | 27 | post_install do |installer| 28 | # https://github.com/facebook/react-native/blob/main/packages/react-native/scripts/react_native_pods.rb#L197-L202 29 | react_native_post_install( 30 | installer, 31 | config[:reactNativePath], 32 | :mac_catalyst_enabled => false, 33 | # :ccache_enabled => true 34 | ) 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /packages/react-native-nitro-image/src/OptionalWebLoader.ts: -------------------------------------------------------------------------------- 1 | // biome-ignore lint/suspicious/noTsIgnore: Type Compilation is a race-condition 2 | // @ts-ignore 3 | type WebImagesType = typeof import("react-native-nitro-web-image")["WebImages"]; 4 | 5 | let createWebImageLoader: WebImagesType["createWebImageLoader"] = () => { 6 | throw new Error( 7 | `Web Images are not supported because react-native-nitro-web-image is not installed!`, 8 | ); 9 | }; 10 | let loadFromURLAsync: WebImagesType["loadFromURLAsync"] = () => { 11 | throw new Error( 12 | `Web Images are not supported because react-native-nitro-web-image is not installed!`, 13 | ); 14 | }; 15 | 16 | export type OptionalAsyncOptions = Parameters< 17 | WebImagesType["loadFromURLAsync"] 18 | >[1]; 19 | 20 | try { 21 | const WebImages = require("react-native-nitro-web-image") 22 | .WebImages as WebImagesType; 23 | createWebImageLoader = WebImages.createWebImageLoader.bind(WebImages); 24 | loadFromURLAsync = WebImages.loadFromURLAsync.bind(WebImages); 25 | } catch { 26 | // react-native-nitro-web-image is not installed, so only local images are supported. 27 | } 28 | 29 | export const OptionalWebImages = { createWebImageLoader, loadFromURLAsync }; 30 | -------------------------------------------------------------------------------- /packages/react-native-nitro-web-image/ios/SDWebImageManager+loadImage.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SDWebImageManager+loadImage.swift 3 | // NitroWebImage 4 | // 5 | // Created by Marc Rousavy on 30.06.25. 6 | // 7 | 8 | import Foundation 9 | import SDWebImage 10 | import NitroModules 11 | 12 | extension SDWebImageManager { 13 | func loadImage(with url: URL, options: AsyncImageLoadOptions?) async throws -> UIImage { 14 | let webImageOptions = options?.toSDWebImageOptions() ?? [] 15 | let webImageContext = options?.toSDWebImageContext() ?? [:] 16 | 17 | return try await withUnsafeThrowingContinuation { continuation in 18 | self.loadImage(with: url, options: webImageOptions, context: webImageContext) { current, total, url in 19 | print("\(url): Loaded \(current)/\(total) bytes") 20 | } completed: { image, data, error, cacheType, finished, url in 21 | if let image { 22 | continuation.resume(returning: image) 23 | } else { 24 | if let error { 25 | continuation.resume(throwing: error) 26 | } else { 27 | continuation.resume(throwing: RuntimeError.error(withMessage: "No Image or error was returned!")) 28 | } 29 | } 30 | } 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /packages/react-native-nitro-image/nitrogen/generated/android/kotlin/com/margelo/nitro/image/RawPixelData.kt: -------------------------------------------------------------------------------- 1 | /// 2 | /// RawPixelData.kt 3 | /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. 4 | /// https://github.com/mrousavy/nitro 5 | /// Copyright © 2025 Marc Rousavy @ Margelo 6 | /// 7 | 8 | package com.margelo.nitro.image 9 | 10 | import androidx.annotation.Keep 11 | import com.facebook.proguard.annotations.DoNotStrip 12 | import com.margelo.nitro.core.ArrayBuffer 13 | 14 | /** 15 | * Represents the JavaScript object/struct "RawPixelData". 16 | */ 17 | @DoNotStrip 18 | @Keep 19 | data class RawPixelData( 20 | @DoNotStrip 21 | @Keep 22 | val buffer: ArrayBuffer, 23 | @DoNotStrip 24 | @Keep 25 | val width: Double, 26 | @DoNotStrip 27 | @Keep 28 | val height: Double, 29 | @DoNotStrip 30 | @Keep 31 | val pixelFormat: PixelFormat 32 | ) { 33 | private companion object { 34 | /** 35 | * Constructor called from C++ 36 | */ 37 | @DoNotStrip 38 | @Keep 39 | @Suppress("unused") 40 | @JvmStatic 41 | private fun fromCpp(buffer: ArrayBuffer, width: Double, height: Double, pixelFormat: PixelFormat): RawPixelData { 42 | return RawPixelData(buffer, width, height, pixelFormat) 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /packages/react-native-nitro-image/nitrogen/generated/shared/c++/HybridNitroImageViewSpec.cpp: -------------------------------------------------------------------------------- 1 | /// 2 | /// HybridNitroImageViewSpec.cpp 3 | /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. 4 | /// https://github.com/mrousavy/nitro 5 | /// Copyright © 2025 Marc Rousavy @ Margelo 6 | /// 7 | 8 | #include "HybridNitroImageViewSpec.hpp" 9 | 10 | namespace margelo::nitro::image { 11 | 12 | void HybridNitroImageViewSpec::loadHybridMethods() { 13 | // load base methods/properties 14 | HybridObject::loadHybridMethods(); 15 | // load custom methods/properties 16 | registerHybrids(this, [](Prototype& prototype) { 17 | prototype.registerHybridGetter("image", &HybridNitroImageViewSpec::getImage); 18 | prototype.registerHybridSetter("image", &HybridNitroImageViewSpec::setImage); 19 | prototype.registerHybridGetter("resizeMode", &HybridNitroImageViewSpec::getResizeMode); 20 | prototype.registerHybridSetter("resizeMode", &HybridNitroImageViewSpec::setResizeMode); 21 | prototype.registerHybridGetter("recyclingKey", &HybridNitroImageViewSpec::getRecyclingKey); 22 | prototype.registerHybridSetter("recyclingKey", &HybridNitroImageViewSpec::setRecyclingKey); 23 | }); 24 | } 25 | 26 | } // namespace margelo::nitro::image 27 | -------------------------------------------------------------------------------- /packages/react-native-nitro-web-image/nitrogen/generated/ios/swift/AsyncImagePriority.swift: -------------------------------------------------------------------------------- 1 | /// 2 | /// AsyncImagePriority.swift 3 | /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. 4 | /// https://github.com/mrousavy/nitro 5 | /// Copyright © 2025 Marc Rousavy @ Margelo 6 | /// 7 | 8 | /** 9 | * Represents the JS union `AsyncImagePriority`, backed by a C++ enum. 10 | */ 11 | public typealias AsyncImagePriority = margelo.nitro.web.image.AsyncImagePriority 12 | 13 | public extension AsyncImagePriority { 14 | /** 15 | * Get a AsyncImagePriority for the given String value, or 16 | * return `nil` if the given value was invalid/unknown. 17 | */ 18 | init?(fromString string: String) { 19 | switch string { 20 | case "low": 21 | self = .low 22 | case "default": 23 | self = .default 24 | case "high": 25 | self = .high 26 | default: 27 | return nil 28 | } 29 | } 30 | 31 | /** 32 | * Get the String value this AsyncImagePriority represents. 33 | */ 34 | var stringValue: String { 35 | switch self { 36 | case .low: 37 | return "low" 38 | case .default: 39 | return "default" 40 | case .high: 41 | return "high" 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /packages/react-native-nitro-image/nitrogen/generated/android/kotlin/com/margelo/nitro/image/NitroImageOnLoad.kt: -------------------------------------------------------------------------------- 1 | /// 2 | /// NitroImageOnLoad.kt 3 | /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. 4 | /// https://github.com/mrousavy/nitro 5 | /// Copyright © 2025 Marc Rousavy @ Margelo 6 | /// 7 | 8 | package com.margelo.nitro.image 9 | 10 | import android.util.Log 11 | 12 | internal class NitroImageOnLoad { 13 | companion object { 14 | private const val TAG = "NitroImageOnLoad" 15 | private var didLoad = false 16 | /** 17 | * Initializes the native part of "NitroImage". 18 | * This method is idempotent and can be called more than once. 19 | */ 20 | @JvmStatic 21 | fun initializeNative() { 22 | if (didLoad) return 23 | try { 24 | Log.i(TAG, "Loading NitroImage C++ library...") 25 | System.loadLibrary("NitroImage") 26 | Log.i(TAG, "Successfully loaded NitroImage C++ library!") 27 | didLoad = true 28 | } catch (e: Error) { 29 | Log.e(TAG, "Failed to load NitroImage C++ library! Is it properly installed and linked? " + 30 | "Is the name correct? (see `CMakeLists.txt`, at `add_library(...)`)", e) 31 | throw e 32 | } 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /packages/react-native-nitro-image/nitrogen/generated/android/kotlin/com/margelo/nitro/image/EncodedImageData.kt: -------------------------------------------------------------------------------- 1 | /// 2 | /// EncodedImageData.kt 3 | /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. 4 | /// https://github.com/mrousavy/nitro 5 | /// Copyright © 2025 Marc Rousavy @ Margelo 6 | /// 7 | 8 | package com.margelo.nitro.image 9 | 10 | import androidx.annotation.Keep 11 | import com.facebook.proguard.annotations.DoNotStrip 12 | import com.margelo.nitro.core.ArrayBuffer 13 | 14 | /** 15 | * Represents the JavaScript object/struct "EncodedImageData". 16 | */ 17 | @DoNotStrip 18 | @Keep 19 | data class EncodedImageData( 20 | @DoNotStrip 21 | @Keep 22 | val buffer: ArrayBuffer, 23 | @DoNotStrip 24 | @Keep 25 | val width: Double, 26 | @DoNotStrip 27 | @Keep 28 | val height: Double, 29 | @DoNotStrip 30 | @Keep 31 | val imageFormat: ImageFormat 32 | ) { 33 | private companion object { 34 | /** 35 | * Constructor called from C++ 36 | */ 37 | @DoNotStrip 38 | @Keep 39 | @Suppress("unused") 40 | @JvmStatic 41 | private fun fromCpp(buffer: ArrayBuffer, width: Double, height: Double, imageFormat: ImageFormat): EncodedImageData { 42 | return EncodedImageData(buffer, width, height, imageFormat) 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /packages/react-native-nitro-image/android/src/main/java/com/margelo/nitro/image/Bitmap+saveToFile.kt: -------------------------------------------------------------------------------- 1 | package com.margelo.nitro.image 2 | 3 | import android.graphics.Bitmap 4 | import java.io.File 5 | import java.io.FileOutputStream 6 | 7 | fun ImageFormat.toBitmapFormat(): Bitmap.CompressFormat { 8 | return when (this) { 9 | ImageFormat.JPG -> Bitmap.CompressFormat.JPEG 10 | ImageFormat.PNG -> Bitmap.CompressFormat.PNG 11 | ImageFormat.HEIC -> { 12 | throw Error("Saving Images as HEIC is not yet supported on Android!") 13 | } 14 | } 15 | } 16 | 17 | fun Bitmap.saveToFile(path: String, format: ImageFormat, quality: Int) { 18 | if (quality < 0 || quality > 100) { 19 | throw Error("Image quality has to be between 0 and 100! (Received: $quality)") 20 | } 21 | // 1. Make sure all parent directories exist 22 | File(path).parentFile?.mkdirs() 23 | // 2. Create a file output stream 24 | FileOutputStream(path).use { out -> 25 | val bitmapFormat = format.toBitmapFormat() 26 | val successful = this.compress(bitmapFormat, quality, out) 27 | if (!successful) { 28 | throw Error("Failed to compress ${width}x${height} Image to file (\"$path\")! (Format: $format, Quality: $quality)") 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /packages/react-native-nitro-web-image/nitrogen/generated/android/kotlin/com/margelo/nitro/web/image/NitroWebImageOnLoad.kt: -------------------------------------------------------------------------------- 1 | /// 2 | /// NitroWebImageOnLoad.kt 3 | /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. 4 | /// https://github.com/mrousavy/nitro 5 | /// Copyright © 2025 Marc Rousavy @ Margelo 6 | /// 7 | 8 | package com.margelo.nitro.web.image 9 | 10 | import android.util.Log 11 | 12 | internal class NitroWebImageOnLoad { 13 | companion object { 14 | private const val TAG = "NitroWebImageOnLoad" 15 | private var didLoad = false 16 | /** 17 | * Initializes the native part of "NitroWebImage". 18 | * This method is idempotent and can be called more than once. 19 | */ 20 | @JvmStatic 21 | fun initializeNative() { 22 | if (didLoad) return 23 | try { 24 | Log.i(TAG, "Loading NitroWebImage C++ library...") 25 | System.loadLibrary("NitroWebImage") 26 | Log.i(TAG, "Successfully loaded NitroWebImage C++ library!") 27 | didLoad = true 28 | } catch (e: Error) { 29 | Log.e(TAG, "Failed to load NitroWebImage C++ library! Is it properly installed and linked? " + 30 | "Is the name correct? (see `CMakeLists.txt`, at `add_library(...)`)", e) 31 | throw e 32 | } 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /packages/react-native-nitro-image/ios/HybridImageUtils.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HybridImageFactory.swift 3 | // react-native-nitro-image 4 | // 5 | // Created by Marc Rousavy on 10.06.25. 6 | // 7 | 8 | import Foundation 9 | import NitroModules 10 | import UniformTypeIdentifiers 11 | 12 | class HybridImageUtils: HybridImageUtilsSpec { 13 | var supportsHeicLoading: Bool { 14 | // Check if the type is supported by the OS 15 | let types = CGImageDestinationCopyTypeIdentifiers() as! [String] 16 | return types.contains(UTType.heic.identifier) 17 | } 18 | var supportsHeicWriting: Bool { 19 | // HEIC .heicData() is only available on iOS 17 20 | if #available(iOS 17.0, *) { 21 | return true 22 | } else { 23 | return false 24 | } 25 | } 26 | 27 | func thumbHashToBase64String(thumbhash: ArrayBuffer) throws -> String { 28 | let data = thumbhash.toData(copyIfNeeded: false) 29 | return data.base64EncodedString() 30 | } 31 | 32 | func thumbhashFromBase64String(thumbhashBase64: String) throws -> ArrayBuffer { 33 | guard let data = Data(base64Encoded: thumbhashBase64) else { 34 | throw RuntimeError.error(withMessage: "The given ThumbHash (\(thumbhashBase64)) is not a valid Base64 encoded Hash!") 35 | } 36 | return try ArrayBuffer.copy(data: data) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /packages/react-native-nitro-image/nitrogen/generated/ios/swift/ResizeMode.swift: -------------------------------------------------------------------------------- 1 | /// 2 | /// ResizeMode.swift 3 | /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. 4 | /// https://github.com/mrousavy/nitro 5 | /// Copyright © 2025 Marc Rousavy @ Margelo 6 | /// 7 | 8 | /** 9 | * Represents the JS union `ResizeMode`, backed by a C++ enum. 10 | */ 11 | public typealias ResizeMode = margelo.nitro.image.ResizeMode 12 | 13 | public extension ResizeMode { 14 | /** 15 | * Get a ResizeMode for the given String value, or 16 | * return `nil` if the given value was invalid/unknown. 17 | */ 18 | init?(fromString string: String) { 19 | switch string { 20 | case "cover": 21 | self = .cover 22 | case "contain": 23 | self = .contain 24 | case "center": 25 | self = .center 26 | case "stretch": 27 | self = .stretch 28 | default: 29 | return nil 30 | } 31 | } 32 | 33 | /** 34 | * Get the String value this ResizeMode represents. 35 | */ 36 | var stringValue: String { 37 | switch self { 38 | case .cover: 39 | return "cover" 40 | case .contain: 41 | return "contain" 42 | case .center: 43 | return "center" 44 | case .stretch: 45 | return "stretch" 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /packages/react-native-nitro-web-image/android/src/main/java/com/margelo/nitro/web/image/HybridWebImageLoader.kt: -------------------------------------------------------------------------------- 1 | package com.margelo.nitro.web.image 2 | 3 | import android.content.Context 4 | import android.widget.ImageView 5 | import coil3.ImageLoader 6 | import coil3.load 7 | import com.margelo.nitro.core.Promise 8 | import com.margelo.nitro.image.HybridImageSpec 9 | import com.margelo.nitro.image.HybridImageLoaderSpec 10 | import com.margelo.nitro.image.HybridNitroImageViewSpec 11 | 12 | class HybridWebImageLoader(private val imageLoader: ImageLoader, 13 | private val url: String, 14 | private val options: AsyncImageLoadOptions?, 15 | private val context: Context) : HybridImageLoaderSpec() { 16 | override fun loadImage(): Promise { 17 | return imageLoader.loadImageAsync(url, options, context) 18 | } 19 | 20 | override fun requestImage(forView: HybridNitroImageViewSpec) { 21 | val imageView = forView.view as? ImageView ?: return 22 | 23 | imageView.load(url, imageLoader) { 24 | this.applyOptions(options) 25 | } 26 | } 27 | 28 | override fun dropImage(forView: HybridNitroImageViewSpec) { 29 | // Coil automatically handles recycling here - I _think_. 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /packages/react-native-nitro-image/src/NitroImage.tsx: -------------------------------------------------------------------------------- 1 | // biome-ignore lint/correctness/noUnusedImports: Needed for JSX runtime 2 | import React from "react"; 3 | import type { HostComponent } from "react-native"; 4 | import type { AsyncImageSource } from "./AsyncImageSource"; 5 | import { NativeNitroImage } from "./NativeNitroImage"; 6 | import { useImageLoader } from "./useImageLoader"; 7 | 8 | type ReactProps = T extends HostComponent ? P : never; 9 | type NativeImageProps = ReactProps; 10 | 11 | export interface NitroImageProps extends Omit { 12 | image: AsyncImageSource; 13 | } 14 | 15 | /** 16 | * The renderable asynchronous `` view. 17 | * 18 | * This is a JS-based abstraction on-top of the 19 | * {@linkcode NativeNitroImage | } view to simplify 20 | * image loading. 21 | * @example 22 | * ```tsx 23 | * function App() { 24 | * return ( 25 | * 29 | * ) 30 | * } 31 | * ``` 32 | */ 33 | export function NitroImage({ image, ...props }: NitroImageProps) { 34 | const actualImage = useImageLoader(image); 35 | return ; 36 | } 37 | -------------------------------------------------------------------------------- /packages/react-native-nitro-image/nitrogen/generated/shared/c++/HybridImageLoaderFactorySpec.cpp: -------------------------------------------------------------------------------- 1 | /// 2 | /// HybridImageLoaderFactorySpec.cpp 3 | /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. 4 | /// https://github.com/mrousavy/nitro 5 | /// Copyright © 2025 Marc Rousavy @ Margelo 6 | /// 7 | 8 | #include "HybridImageLoaderFactorySpec.hpp" 9 | 10 | namespace margelo::nitro::image { 11 | 12 | void HybridImageLoaderFactorySpec::loadHybridMethods() { 13 | // load base methods/properties 14 | HybridObject::loadHybridMethods(); 15 | // load custom methods/properties 16 | registerHybrids(this, [](Prototype& prototype) { 17 | prototype.registerHybridMethod("createFileImageLoader", &HybridImageLoaderFactorySpec::createFileImageLoader); 18 | prototype.registerHybridMethod("createResourceImageLoader", &HybridImageLoaderFactorySpec::createResourceImageLoader); 19 | prototype.registerHybridMethod("createSymbolImageLoader", &HybridImageLoaderFactorySpec::createSymbolImageLoader); 20 | prototype.registerHybridMethod("createRawPixelDataImageLoader", &HybridImageLoaderFactorySpec::createRawPixelDataImageLoader); 21 | prototype.registerHybridMethod("createEncodedImageDataImageLoader", &HybridImageLoaderFactorySpec::createEncodedImageDataImageLoader); 22 | }); 23 | } 24 | 25 | } // namespace margelo::nitro::image 26 | -------------------------------------------------------------------------------- /packages/react-native-nitro-image/android/src/main/java/com/margelo/nitro/image/HybridImageLoaderFactory.kt: -------------------------------------------------------------------------------- 1 | package com.margelo.nitro.image 2 | 3 | import androidx.annotation.Keep 4 | import com.facebook.proguard.annotations.DoNotStrip 5 | import com.margelo.nitro.core.ArrayBuffer 6 | import com.margelo.nitro.core.Promise 7 | 8 | @Keep 9 | @DoNotStrip 10 | class HybridImageLoaderFactory: HybridImageLoaderFactorySpec() { 11 | private val factory = HybridImageFactory() 12 | 13 | override fun createFileImageLoader(filePath: String): HybridImageLoaderSpec { 14 | return HybridImageLoader { factory.loadFromFileAsync(filePath) } 15 | } 16 | 17 | override fun createResourceImageLoader(name: String): HybridImageLoaderSpec { 18 | return HybridImageLoader { factory.loadFromResourcesAsync(name) } 19 | } 20 | 21 | override fun createSymbolImageLoader(symbolName: String): HybridImageLoaderSpec { 22 | return HybridImageLoader { Promise.resolved(factory.loadFromSymbol(symbolName)) } 23 | } 24 | 25 | override fun createRawPixelDataImageLoader(data: RawPixelData): HybridImageLoaderSpec { 26 | return HybridImageLoader { factory.loadFromRawPixelDataAsync(data, false) } 27 | } 28 | 29 | override fun createEncodedImageDataImageLoader(data: EncodedImageData): HybridImageLoaderSpec { 30 | return HybridImageLoader { factory.loadFromEncodedImageDataAsync(data) } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | # OSX 2 | # 3 | .DS_Store 4 | 5 | # Xcode 6 | # 7 | build/ 8 | *.pbxuser 9 | !default.pbxuser 10 | *.mode1v3 11 | !default.mode1v3 12 | *.mode2v3 13 | !default.mode2v3 14 | *.perspectivev3 15 | !default.perspectivev3 16 | xcuserdata 17 | *.xccheckout 18 | *.moved-aside 19 | DerivedData 20 | *.hmap 21 | *.ipa 22 | *.xcuserstate 23 | **/.xcode.env.local 24 | 25 | # Android/IntelliJ 26 | # 27 | build/ 28 | .idea 29 | .gradle 30 | local.properties 31 | *.iml 32 | *.hprof 33 | .cxx/ 34 | *.keystore 35 | !debug.keystore 36 | .kotlin/ 37 | 38 | # node.js 39 | # 40 | node_modules/ 41 | npm-debug.log 42 | yarn-error.log 43 | 44 | # fastlane 45 | # 46 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 47 | # screenshots whenever they are needed. 48 | # For more information about the recommended setup visit: 49 | # https://docs.fastlane.tools/best-practices/source-control/ 50 | 51 | **/fastlane/report.xml 52 | **/fastlane/Preview.html 53 | **/fastlane/screenshots 54 | **/fastlane/test_output 55 | 56 | # Bundle artifact 57 | *.jsbundle 58 | 59 | # Ruby / CocoaPods 60 | **/Pods/ 61 | /vendor/bundle/ 62 | 63 | # Temporary files created by Metro to check the health of the file watcher 64 | .metro-health-check* 65 | 66 | # testing 67 | /coverage 68 | 69 | # Yarn 70 | .yarn/* 71 | !.yarn/patches 72 | !.yarn/plugins 73 | !.yarn/releases 74 | !.yarn/sdks 75 | !.yarn/versions 76 | -------------------------------------------------------------------------------- /packages/react-native-nitro-web-image/android/src/main/java/com/margelo/nitro/web/image/NitroWebImagePackage.java: -------------------------------------------------------------------------------- 1 | package com.margelo.nitro.web.image; 2 | 3 | import android.util.Log; 4 | 5 | import androidx.annotation.NonNull; 6 | import androidx.annotation.Nullable; 7 | 8 | import com.facebook.react.bridge.NativeModule; 9 | import com.facebook.react.bridge.ReactApplicationContext; 10 | import com.facebook.react.module.model.ReactModuleInfoProvider; 11 | import com.facebook.react.TurboReactPackage; 12 | import com.facebook.react.uimanager.ViewManager; 13 | import com.margelo.nitro.core.HybridObject; 14 | 15 | import java.util.ArrayList; 16 | import java.util.HashMap; 17 | import java.util.List; 18 | import java.util.function.Supplier; 19 | 20 | public class NitroWebImagePackage extends TurboReactPackage { 21 | @Nullable 22 | @Override 23 | public NativeModule getModule(String name, ReactApplicationContext reactContext) { 24 | return null; 25 | } 26 | 27 | @Override 28 | public ReactModuleInfoProvider getReactModuleInfoProvider() { 29 | return () -> { 30 | return new HashMap<>(); 31 | }; 32 | } 33 | 34 | 35 | @Override 36 | public List createViewManagers(@NonNull ReactApplicationContext reactContext) { 37 | List viewManagers = new ArrayList<>(); 38 | return viewManagers; 39 | } 40 | 41 | static { 42 | NitroWebImageOnLoad.initializeNative(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /packages/react-native-nitro-image/src/AsyncImageSource.ts: -------------------------------------------------------------------------------- 1 | import type { HybridObject } from "react-native-nitro-modules"; 2 | import type { OptionalAsyncOptions } from "./OptionalWebLoader"; 3 | import type { 4 | EncodedImageData, 5 | Image, 6 | RawPixelData, 7 | } from "./specs/Image.nitro"; 8 | import type { ImageLoader } from "./specs/ImageLoader.nitro"; 9 | 10 | export type RequireType = number; 11 | export type AsyncImageSource = 12 | | Image 13 | | ImageLoader 14 | | { filePath: string } 15 | | { rawPixelData: RawPixelData } 16 | | { encodedImageData: EncodedImageData } 17 | | { resource: string } 18 | | { symbolName: string } 19 | | { url: string; options?: OptionalAsyncOptions } 20 | | RequireType; 21 | 22 | // @ts-expect-error i know what I'm doing 23 | export function isHybridObject(obj: T): obj is HybridObject { 24 | // @ts-expect-error 25 | return typeof obj === "object" && obj != null && obj.dispose != null; 26 | } 27 | // @ts-expect-error i know what I'm doing 28 | export function isHybridImage(obj: T): obj is Image { 29 | // @ts-expect-error 30 | return typeof obj === "object" && obj != null && obj.toRawPixelData != null; 31 | } 32 | export function isHybridImageLoader( 33 | obj: T, 34 | // @ts-expect-error i know what I'm doing 35 | ): obj is ImageLoader { 36 | // @ts-expect-error 37 | return typeof obj === "object" && obj != null && obj.loadImage != null; 38 | } 39 | -------------------------------------------------------------------------------- /example/ios/NitroImageExample/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import React 3 | import React_RCTAppDelegate 4 | import ReactAppDependencyProvider 5 | 6 | @main 7 | class AppDelegate: UIResponder, UIApplicationDelegate { 8 | var window: UIWindow? 9 | 10 | var reactNativeDelegate: ReactNativeDelegate? 11 | var reactNativeFactory: RCTReactNativeFactory? 12 | 13 | func application( 14 | _ application: UIApplication, 15 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil 16 | ) -> Bool { 17 | let delegate = ReactNativeDelegate() 18 | let factory = RCTReactNativeFactory(delegate: delegate) 19 | delegate.dependencyProvider = RCTAppDependencyProvider() 20 | 21 | reactNativeDelegate = delegate 22 | reactNativeFactory = factory 23 | 24 | window = UIWindow(frame: UIScreen.main.bounds) 25 | 26 | factory.startReactNative( 27 | withModuleName: "NitroImageExample", 28 | in: window, 29 | launchOptions: launchOptions 30 | ) 31 | 32 | return true 33 | } 34 | } 35 | 36 | class ReactNativeDelegate: RCTDefaultReactNativeFactoryDelegate { 37 | override func sourceURL(for bridge: RCTBridge) -> URL? { 38 | self.bundleURL() 39 | } 40 | 41 | override func bundleURL() -> URL? { 42 | #if DEBUG 43 | RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: "index") 44 | #else 45 | Bundle.main.url(forResource: "main", withExtension: "jsbundle") 46 | #endif 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /packages/react-native-nitro-image/nitrogen/generated/ios/swift/Func_void.swift: -------------------------------------------------------------------------------- 1 | /// 2 | /// Func_void.swift 3 | /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. 4 | /// https://github.com/mrousavy/nitro 5 | /// Copyright © 2025 Marc Rousavy @ Margelo 6 | /// 7 | 8 | import NitroModules 9 | 10 | 11 | /** 12 | * Wraps a Swift `() -> Void` as a class. 13 | * This class can be used from C++, e.g. to wrap the Swift closure as a `std::function`. 14 | */ 15 | public final class Func_void { 16 | public typealias bridge = margelo.nitro.image.bridge.swift 17 | 18 | private let closure: () -> Void 19 | 20 | public init(_ closure: @escaping () -> Void) { 21 | self.closure = closure 22 | } 23 | 24 | @inline(__always) 25 | public func call() -> Void { 26 | self.closure() 27 | } 28 | 29 | /** 30 | * Casts this instance to a retained unsafe raw pointer. 31 | * This acquires one additional strong reference on the object! 32 | */ 33 | @inline(__always) 34 | public func toUnsafe() -> UnsafeMutableRawPointer { 35 | return Unmanaged.passRetained(self).toOpaque() 36 | } 37 | 38 | /** 39 | * Casts an unsafe pointer to a `Func_void`. 40 | * The pointer has to be a retained opaque `Unmanaged`. 41 | * This removes one strong reference from the object! 42 | */ 43 | @inline(__always) 44 | public static func fromUnsafe(_ pointer: UnsafeMutableRawPointer) -> Func_void { 45 | return Unmanaged.fromOpaque(pointer).takeRetainedValue() 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /packages/react-native-nitro-image/android/src/main/java/com/margelo/nitro/image/HybridImageUtils.kt: -------------------------------------------------------------------------------- 1 | package com.margelo.nitro.image 2 | 3 | import android.os.Build 4 | import androidx.annotation.Keep 5 | import com.facebook.common.internal.DoNotStrip 6 | import com.margelo.nitro.core.ArrayBuffer 7 | import java.nio.ByteBuffer 8 | import kotlin.io.encoding.Base64 9 | import kotlin.io.encoding.ExperimentalEncodingApi 10 | 11 | @DoNotStrip 12 | @Keep 13 | class HybridImageUtils: HybridImageUtilsSpec() { 14 | override val supportsHeicLoading: Boolean 15 | get() { 16 | // Since Android 10, HEIF/HEIC is standard. 17 | // https://source.android.com/docs/core/camera/heif 18 | return Build.VERSION.SDK_INT >= Build.VERSION_CODES.P 19 | } 20 | override val supportsHeicWriting: Boolean 21 | get() { 22 | // Android does not support saving HEIF data yet 23 | return false 24 | } 25 | 26 | 27 | @OptIn(ExperimentalEncodingApi::class) 28 | override fun thumbHashToBase64String(thumbhash: ArrayBuffer): String { 29 | val buffer = thumbhash.toByteArray() 30 | val base64 = Base64.encode(buffer) 31 | return base64 32 | } 33 | 34 | @OptIn(ExperimentalEncodingApi::class) 35 | override fun thumbhashFromBase64String(thumbhashBase64: String): ArrayBuffer { 36 | val bytes = Base64.decode(thumbhashBase64) 37 | val buffer = ByteBuffer.wrap(bytes) 38 | return ArrayBuffer.wrap(buffer) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /packages/react-native-nitro-image/android/src/main/java/com/margelo/nitro/image/NitroImagePackage.java: -------------------------------------------------------------------------------- 1 | package com.margelo.nitro.image; 2 | 3 | import android.util.Log; 4 | 5 | import androidx.annotation.NonNull; 6 | import androidx.annotation.Nullable; 7 | 8 | import com.facebook.react.bridge.NativeModule; 9 | import com.facebook.react.bridge.ReactApplicationContext; 10 | import com.facebook.react.module.model.ReactModuleInfoProvider; 11 | import com.facebook.react.TurboReactPackage; 12 | import com.facebook.react.uimanager.ViewManager; 13 | import com.margelo.nitro.core.HybridObject; 14 | import com.margelo.nitro.image.views.HybridNitroImageViewManager; 15 | 16 | import java.util.ArrayList; 17 | import java.util.HashMap; 18 | import java.util.List; 19 | import java.util.function.Supplier; 20 | 21 | public class NitroImagePackage extends TurboReactPackage { 22 | @Nullable 23 | @Override 24 | public NativeModule getModule(String name, ReactApplicationContext reactContext) { 25 | return null; 26 | } 27 | 28 | @Override 29 | public ReactModuleInfoProvider getReactModuleInfoProvider() { 30 | return () -> { 31 | return new HashMap<>(); 32 | }; 33 | } 34 | 35 | 36 | @Override 37 | public List createViewManagers(@NonNull ReactApplicationContext reactContext) { 38 | List viewManagers = new ArrayList<>(); 39 | viewManagers.add(new HybridNitroImageViewManager()); 40 | return viewManagers; 41 | } 42 | 43 | static { 44 | NitroImageOnLoad.initializeNative(); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /packages/react-native-nitro-image/ios/CGImage+getPixelFormat.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CGImage+pixelFormat.swift 3 | // react-native-nitro-image 4 | // 5 | // Created by Marc Rousavy on 22.10.25. 6 | // 7 | 8 | import Foundation 9 | import CoreGraphics 10 | 11 | extension CGImage { 12 | private var isLittleEndian: Bool { 13 | switch self.byteOrderInfo { 14 | case .orderDefault: 15 | // iOS uses little endian by default 16 | return true 17 | case .order16Little, .order32Little: 18 | return true 19 | case .order16Big, .order32Big: 20 | return false 21 | case .orderMask: 22 | fatalError(".orderMask is an unknown value for endianness!") 23 | @unknown default: 24 | fatalError("CGImage has unknown .byteOrderInfo!") 25 | } 26 | } 27 | 28 | var pixelFormat: PixelFormat { 29 | guard self.bitsPerComponent == 8, self.bitsPerPixel == 32 else { 30 | return .unknown 31 | } 32 | switch self.alphaInfo { 33 | case .premultipliedFirst, .first: 34 | // A___ 35 | return self.isLittleEndian ? .bgra : .argb 36 | case .premultipliedLast, .last: 37 | // ___A 38 | return self.isLittleEndian ? .abgr : .rgba 39 | case .noneSkipFirst: 40 | // X___ 41 | return self.isLittleEndian ? .bgra : .argb 42 | case .noneSkipLast: 43 | // ___X 44 | return self.isLittleEndian ? .abgr : .rgba 45 | case .none: 46 | // ___ 47 | return self.isLittleEndian ? .bgr : .rgb 48 | default: 49 | return .unknown 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /packages/react-native-nitro-image/ios/HybridImageLoaderFactory.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HybridImageLoaderFactory.swift 3 | // Pods 4 | // 5 | // Created by Marc Rousavy on 25.07.25. 6 | // 7 | 8 | import NitroModules 9 | 10 | class HybridImageLoaderFactory: HybridImageLoaderFactorySpec { 11 | private let imageFactory = HybridImageFactory() 12 | 13 | func createFileImageLoader(filePath: String) throws -> any HybridImageLoaderSpec { 14 | return HybridImageLoader(load: { 15 | try self.imageFactory.loadFromFileAsync(filePath: filePath) 16 | }) 17 | } 18 | 19 | func createResourceImageLoader(name: String) throws -> any HybridImageLoaderSpec { 20 | return HybridImageLoader(load: { 21 | try self.imageFactory.loadFromResourcesAsync(name: name) 22 | }) 23 | } 24 | 25 | func createSymbolImageLoader(symbolName: String) throws -> any HybridImageLoaderSpec { 26 | return HybridImageLoader(load: { 27 | let image = try self.imageFactory.loadFromSymbol(symbolName: symbolName) 28 | return Promise.resolved(withResult: image) 29 | }) 30 | } 31 | 32 | func createRawPixelDataImageLoader(data: RawPixelData) throws -> any HybridImageLoaderSpec { 33 | return HybridImageLoader(load: { 34 | try self.imageFactory.loadFromRawPixelDataAsync(data: data, allowGpu: false) 35 | }) 36 | } 37 | 38 | func createEncodedImageDataImageLoader(data: EncodedImageData) throws -> any HybridImageLoaderSpec { 39 | return HybridImageLoader(load: { 40 | try self.imageFactory.loadFromEncodedImageDataAsync(data: data) 41 | }) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /example/android/app/src/main/java/com/nitroimageexample/MainApplication.kt: -------------------------------------------------------------------------------- 1 | package com.nitroimageexample 2 | 3 | import android.app.Application 4 | import com.facebook.react.PackageList 5 | import com.facebook.react.ReactApplication 6 | import com.facebook.react.ReactHost 7 | import com.facebook.react.ReactNativeApplicationEntryPoint.loadReactNative 8 | import com.facebook.react.ReactNativeHost 9 | import com.facebook.react.ReactPackage 10 | import com.facebook.react.defaults.DefaultReactHost.getDefaultReactHost 11 | import com.facebook.react.defaults.DefaultReactNativeHost 12 | 13 | class MainApplication : Application(), ReactApplication { 14 | 15 | override val reactNativeHost: ReactNativeHost = 16 | object : DefaultReactNativeHost(this) { 17 | override fun getPackages(): List = 18 | PackageList(this).packages.apply { 19 | // Packages that cannot be autolinked yet can be added manually here, for example: 20 | // add(MyReactNativePackage()) 21 | } 22 | 23 | override fun getJSMainModuleName(): String = "index" 24 | 25 | override fun getUseDeveloperSupport(): Boolean = BuildConfig.DEBUG 26 | 27 | override val isNewArchEnabled: Boolean = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED 28 | override val isHermesEnabled: Boolean = BuildConfig.IS_HERMES_ENABLED 29 | } 30 | 31 | override val reactHost: ReactHost 32 | get() = getDefaultReactHost(applicationContext, reactNativeHost) 33 | 34 | override fun onCreate() { 35 | super.onCreate() 36 | loadReactNative(this) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /packages/react-native-nitro-web-image/android/src/main/java/com/margelo/nitro/web/image/ImageLoader+loadImageAsync.kt: -------------------------------------------------------------------------------- 1 | package com.margelo.nitro.web.image 2 | 3 | import android.content.Context 4 | import coil3.BitmapImage 5 | import coil3.Image 6 | import coil3.ImageLoader 7 | import coil3.request.ImageRequest 8 | import com.margelo.nitro.core.Promise 9 | import com.margelo.nitro.image.HybridImage 10 | import com.margelo.nitro.image.HybridImageSpec 11 | 12 | suspend fun ImageLoader.loadCoilImageAsync(url: String, 13 | options: AsyncImageLoadOptions?, 14 | context: Context): Image { 15 | // 1. Create the Coil Request 16 | val request = ImageRequest.Builder(context) 17 | .data(url) 18 | .applyOptions(options) 19 | .build() 20 | // 2. Execute it (async) 21 | val result = this.execute(request) 22 | val image = result.image ?: throw Error("Failed to load Image!") 23 | return image 24 | } 25 | 26 | fun ImageLoader.loadImageAsync(url: String, 27 | options: AsyncImageLoadOptions?, 28 | context: Context 29 | ): Promise { 30 | return Promise.async { 31 | // 1. Load the coil image 32 | val image = loadCoilImageAsync(url, options, context) 33 | // 3. Downcast to a Bitmap - if that fails, it might be an Animated Image... 34 | val bitmap = image as? BitmapImage ?: throw Error("Requested Image is not a Bitmap!") 35 | return@async HybridImage(bitmap.bitmap) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /packages/react-native-nitro-image/nitrogen/generated/android/c++/JVariant_HybridImageSpec_HybridImageLoaderSpec.cpp: -------------------------------------------------------------------------------- 1 | /// 2 | /// JVariant_HybridImageSpec_HybridImageLoaderSpec.cpp 3 | /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. 4 | /// https://github.com/mrousavy/nitro 5 | /// Copyright © 2025 Marc Rousavy @ Margelo 6 | /// 7 | 8 | #include "JVariant_HybridImageSpec_HybridImageLoaderSpec.hpp" 9 | 10 | namespace margelo::nitro::image { 11 | /** 12 | * Converts JVariant_HybridImageSpec_HybridImageLoaderSpec to std::variant, std::shared_ptr> 13 | */ 14 | std::variant, std::shared_ptr> JVariant_HybridImageSpec_HybridImageLoaderSpec::toCpp() const { 15 | if (isInstanceOf(JVariant_HybridImageSpec_HybridImageLoaderSpec_impl::First::javaClassStatic())) { 16 | // It's a `std::shared_ptr` 17 | auto jniValue = static_cast(this)->getValue(); 18 | return jniValue->cthis()->shared_cast(); 19 | } else if (isInstanceOf(JVariant_HybridImageSpec_HybridImageLoaderSpec_impl::Second::javaClassStatic())) { 20 | // It's a `std::shared_ptr` 21 | auto jniValue = static_cast(this)->getValue(); 22 | return jniValue->cthis()->shared_cast(); 23 | } 24 | throw std::invalid_argument("Variant is unknown Kotlin instance!"); 25 | } 26 | } // namespace margelo::nitro::image 27 | -------------------------------------------------------------------------------- /packages/react-native-nitro-web-image/ios/HybridWebImageFactory.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HybridWebImageFactory.swift 3 | // react-native-nitro-web-image 4 | // 5 | // Created by Marc Rousavy on 10.06.25. 6 | // 7 | 8 | import Foundation 9 | import NitroModules 10 | import SDWebImage 11 | import NitroImage 12 | 13 | class HybridWebImageFactory: HybridWebImageFactorySpec { 14 | func loadFromURLAsync(url urlString: String, options: AsyncImageLoadOptions?) throws -> Promise { 15 | guard let url = URL(string: urlString) else { 16 | throw RuntimeError.error(withMessage: "URL string \"\(urlString)\" is not a valid URL!") 17 | } 18 | 19 | return HybridWebImageLoader.loadImage(url: url, options: options) 20 | } 21 | 22 | private let queue = DispatchQueue(label: "image-loader", 23 | qos: .default, 24 | attributes: .concurrent) 25 | 26 | /** 27 | * Load Image from URL 28 | */ 29 | func createWebImageLoader(url urlString: String, options: AsyncImageLoadOptions?) throws -> any HybridImageLoaderSpec { 30 | guard let url = URL(string: urlString) else { 31 | throw RuntimeError.error(withMessage: "URL string \"\(urlString)\" is not a valid URL!") 32 | } 33 | 34 | return HybridWebImageLoader(url: url, options: options) 35 | } 36 | 37 | func preload(url urlString: String) throws { 38 | guard let url = URL(string: urlString) else { 39 | throw RuntimeError.error(withMessage: "URL string \"\(urlString)\" is not a valid URL!") 40 | } 41 | SDWebImagePrefetcher.shared.prefetchURLs([url]) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /.github/workflows/build-android-release.yml: -------------------------------------------------------------------------------- 1 | name: Build Android (Release) 2 | 3 | on: 4 | release: 5 | types: [published] 6 | pull_request: 7 | paths: 8 | - '.github/workflows/build-android-release.yml' 9 | 10 | jobs: 11 | build_release: 12 | name: Build Android Example App (release) 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v4 16 | - uses: oven-sh/setup-bun@v2 17 | 18 | - name: Install npm dependencies (bun) 19 | run: bun install 20 | - name: Install npm dependencies in example/ (bun) 21 | working-directory: example 22 | run: bun install 23 | 24 | - name: Setup JDK 17 25 | uses: actions/setup-java@v4 26 | with: 27 | distribution: 'zulu' 28 | java-version: 17 29 | java-package: jdk 30 | 31 | - name: Run Gradle Build for example/android/ 32 | working-directory: example 33 | run: bun run build:android-release 34 | 35 | - name: Upload APK to Release 36 | uses: actions/upload-release-asset@v1 37 | env: 38 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 39 | with: 40 | upload_url: ${{ github.event.release.upload_url }} 41 | asset_path: example/android/app/build/outputs/apk/release/app-release.apk 42 | asset_name: "NitroImageExample-${{ github.event.release.tag_name }}.apk" 43 | asset_content_type: application/vnd.android.package-archive 44 | 45 | # Gradle cache doesn't like daemons 46 | - name: Stop Gradle Daemon 47 | working-directory: example/android 48 | run: ./gradlew --stop 49 | -------------------------------------------------------------------------------- /packages/react-native-nitro-web-image/android/src/main/java/com/margelo/nitro/web/image/HybridWebImageFactory.kt: -------------------------------------------------------------------------------- 1 | package com.margelo.nitro.web.image 2 | 3 | import androidx.annotation.Keep 4 | import coil3.ImageLoader 5 | import coil3.request.ImageRequest 6 | import com.facebook.common.internal.DoNotStrip 7 | import com.facebook.react.bridge.ReactApplicationContext 8 | import com.margelo.nitro.NitroModules 9 | import com.margelo.nitro.core.Promise 10 | import com.margelo.nitro.image.HybridImageLoaderSpec 11 | import com.margelo.nitro.image.HybridImageSpec 12 | 13 | @DoNotStrip 14 | @Keep 15 | class HybridWebImageFactory: HybridWebImageFactorySpec() { 16 | private val context: ReactApplicationContext 17 | get() = NitroModules.applicationContext ?: throw Error("No context - NitroModules.applicationContext was null!") 18 | private val imageLoader = ImageLoader(context) 19 | 20 | override fun createWebImageLoader( 21 | url: String, 22 | options: AsyncImageLoadOptions? 23 | ): HybridImageLoaderSpec { 24 | return HybridWebImageLoader(imageLoader, url, options, context) 25 | } 26 | 27 | override fun loadFromURLAsync( 28 | url: String, 29 | options: AsyncImageLoadOptions? 30 | ): Promise { 31 | return imageLoader.loadImageAsync(url, options, context) 32 | } 33 | 34 | override fun preload(url: String) { 35 | // 1. Create the Coil Request 36 | val request = ImageRequest.Builder(context) 37 | .data(url) 38 | .build() 39 | // 2. Enqueue the request to prefetch it 40 | imageLoader.enqueue(request) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /packages/react-native-nitro-image/nitrogen/generated/ios/swift/Func_void_std__string.swift: -------------------------------------------------------------------------------- 1 | /// 2 | /// Func_void_std__string.swift 3 | /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. 4 | /// https://github.com/mrousavy/nitro 5 | /// Copyright © 2025 Marc Rousavy @ Margelo 6 | /// 7 | 8 | import NitroModules 9 | 10 | 11 | /** 12 | * Wraps a Swift `(_ value: String) -> Void` as a class. 13 | * This class can be used from C++, e.g. to wrap the Swift closure as a `std::function`. 14 | */ 15 | public final class Func_void_std__string { 16 | public typealias bridge = margelo.nitro.image.bridge.swift 17 | 18 | private let closure: (_ value: String) -> Void 19 | 20 | public init(_ closure: @escaping (_ value: String) -> Void) { 21 | self.closure = closure 22 | } 23 | 24 | @inline(__always) 25 | public func call(value: std.string) -> Void { 26 | self.closure(String(value)) 27 | } 28 | 29 | /** 30 | * Casts this instance to a retained unsafe raw pointer. 31 | * This acquires one additional strong reference on the object! 32 | */ 33 | @inline(__always) 34 | public func toUnsafe() -> UnsafeMutableRawPointer { 35 | return Unmanaged.passRetained(self).toOpaque() 36 | } 37 | 38 | /** 39 | * Casts an unsafe pointer to a `Func_void_std__string`. 40 | * The pointer has to be a retained opaque `Unmanaged`. 41 | * This removes one strong reference from the object! 42 | */ 43 | @inline(__always) 44 | public static func fromUnsafe(_ pointer: UnsafeMutableRawPointer) -> Func_void_std__string { 45 | return Unmanaged.fromOpaque(pointer).takeRetainedValue() 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /packages/react-native-nitro-web-image/nitrogen/generated/android/NitroWebImageOnLoad.cpp: -------------------------------------------------------------------------------- 1 | /// 2 | /// NitroWebImageOnLoad.cpp 3 | /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. 4 | /// https://github.com/mrousavy/nitro 5 | /// Copyright © 2025 Marc Rousavy @ Margelo 6 | /// 7 | 8 | #ifndef BUILDING_NITROWEBIMAGE_WITH_GENERATED_CMAKE_PROJECT 9 | #error NitroWebImageOnLoad.cpp is not being built with the autogenerated CMakeLists.txt project. Is a different CMakeLists.txt building this? 10 | #endif 11 | 12 | #include "NitroWebImageOnLoad.hpp" 13 | 14 | #include 15 | #include 16 | #include 17 | 18 | #include "JHybridWebImageFactorySpec.hpp" 19 | #include 20 | 21 | namespace margelo::nitro::web::image { 22 | 23 | int initialize(JavaVM* vm) { 24 | using namespace margelo::nitro; 25 | using namespace margelo::nitro::web::image; 26 | using namespace facebook; 27 | 28 | return facebook::jni::initialize(vm, [] { 29 | // Register native JNI methods 30 | margelo::nitro::web::image::JHybridWebImageFactorySpec::registerNatives(); 31 | 32 | // Register Nitro Hybrid Objects 33 | HybridObjectRegistry::registerHybridObjectConstructor( 34 | "WebImageFactory", 35 | []() -> std::shared_ptr { 36 | static DefaultConstructableObject object("com/margelo/nitro/web/image/HybridWebImageFactory"); 37 | auto instance = object.create(); 38 | return instance->cthis()->shared(); 39 | } 40 | ); 41 | }); 42 | } 43 | 44 | } // namespace margelo::nitro::web::image 45 | -------------------------------------------------------------------------------- /packages/react-native-nitro-image/nitrogen/generated/ios/swift/Func_void_RawPixelData.swift: -------------------------------------------------------------------------------- 1 | /// 2 | /// Func_void_RawPixelData.swift 3 | /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. 4 | /// https://github.com/mrousavy/nitro 5 | /// Copyright © 2025 Marc Rousavy @ Margelo 6 | /// 7 | 8 | import NitroModules 9 | 10 | 11 | /** 12 | * Wraps a Swift `(_ value: RawPixelData) -> Void` as a class. 13 | * This class can be used from C++, e.g. to wrap the Swift closure as a `std::function`. 14 | */ 15 | public final class Func_void_RawPixelData { 16 | public typealias bridge = margelo.nitro.image.bridge.swift 17 | 18 | private let closure: (_ value: RawPixelData) -> Void 19 | 20 | public init(_ closure: @escaping (_ value: RawPixelData) -> Void) { 21 | self.closure = closure 22 | } 23 | 24 | @inline(__always) 25 | public func call(value: RawPixelData) -> Void { 26 | self.closure(value) 27 | } 28 | 29 | /** 30 | * Casts this instance to a retained unsafe raw pointer. 31 | * This acquires one additional strong reference on the object! 32 | */ 33 | @inline(__always) 34 | public func toUnsafe() -> UnsafeMutableRawPointer { 35 | return Unmanaged.passRetained(self).toOpaque() 36 | } 37 | 38 | /** 39 | * Casts an unsafe pointer to a `Func_void_RawPixelData`. 40 | * The pointer has to be a retained opaque `Unmanaged`. 41 | * This removes one strong reference from the object! 42 | */ 43 | @inline(__always) 44 | public static func fromUnsafe(_ pointer: UnsafeMutableRawPointer) -> Func_void_RawPixelData { 45 | return Unmanaged.fromOpaque(pointer).takeRetainedValue() 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /packages/react-native-nitro-web-image/ios/AsyncImageLoadOptions+toSDWebImageOptions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AsyncImageLoadOptions+toSDWebImageOptions.swift 3 | // NitroWebImage 4 | // 5 | // Created by Marc Rousavy on 30.06.25. 6 | // 7 | 8 | import Foundation 9 | import SDWebImage 10 | 11 | extension AsyncImageLoadOptions { 12 | func toSDWebImageOptions() -> SDWebImageOptions { 13 | var options: SDWebImageOptions = [] 14 | 15 | switch priority { 16 | case .default, .none: 17 | break 18 | case .low: 19 | options.insert(.lowPriority) 20 | case .high: 21 | options.insert(.highPriority) 22 | } 23 | 24 | if forceRefresh == true { 25 | options.insert(.refreshCached) 26 | } 27 | 28 | if continueInBackground == true { 29 | options.insert(.continueInBackground) 30 | } 31 | 32 | if allowInvalidSSLCertificates == true { 33 | options.insert(.allowInvalidSSLCertificates) 34 | } 35 | 36 | if scaleDownLargeImages == true { 37 | options.insert(.scaleDownLargeImages) 38 | } 39 | 40 | if queryMemoryDataSync == true { 41 | options.insert(.queryMemoryDataSync) 42 | } 43 | 44 | if queryDiskDataSync == true { 45 | options.insert(.queryDiskDataSync) 46 | } 47 | 48 | if decodeImage == false { 49 | options.insert(.avoidDecodeImage) 50 | } 51 | 52 | return options 53 | } 54 | 55 | func toSDWebImageContext() -> [SDWebImageContextOption: Any] { 56 | var context: [SDWebImageContextOption: Any] = [:] 57 | 58 | if let cacheKey { 59 | context[.cacheKeyFilter] = SDWebImageCacheKeyFilter { _ in 60 | return cacheKey 61 | } 62 | } 63 | 64 | return context 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /packages/react-native-nitro-image/nitrogen/generated/ios/swift/RawPixelData.swift: -------------------------------------------------------------------------------- 1 | /// 2 | /// RawPixelData.swift 3 | /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. 4 | /// https://github.com/mrousavy/nitro 5 | /// Copyright © 2025 Marc Rousavy @ Margelo 6 | /// 7 | 8 | import NitroModules 9 | 10 | /** 11 | * Represents an instance of `RawPixelData`, backed by a C++ struct. 12 | */ 13 | public typealias RawPixelData = margelo.nitro.image.RawPixelData 14 | 15 | public extension RawPixelData { 16 | private typealias bridge = margelo.nitro.image.bridge.swift 17 | 18 | /** 19 | * Create a new instance of `RawPixelData`. 20 | */ 21 | init(buffer: ArrayBuffer, width: Double, height: Double, pixelFormat: PixelFormat) { 22 | self.init(buffer.getArrayBuffer(), width, height, pixelFormat) 23 | } 24 | 25 | var buffer: ArrayBuffer { 26 | @inline(__always) 27 | get { 28 | return ArrayBuffer(self.__buffer) 29 | } 30 | @inline(__always) 31 | set { 32 | self.__buffer = newValue.getArrayBuffer() 33 | } 34 | } 35 | 36 | var width: Double { 37 | @inline(__always) 38 | get { 39 | return self.__width 40 | } 41 | @inline(__always) 42 | set { 43 | self.__width = newValue 44 | } 45 | } 46 | 47 | var height: Double { 48 | @inline(__always) 49 | get { 50 | return self.__height 51 | } 52 | @inline(__always) 53 | set { 54 | self.__height = newValue 55 | } 56 | } 57 | 58 | var pixelFormat: PixelFormat { 59 | @inline(__always) 60 | get { 61 | return self.__pixelFormat 62 | } 63 | @inline(__always) 64 | set { 65 | self.__pixelFormat = newValue 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /packages/react-native-nitro-image/nitrogen/generated/ios/swift/Func_void_EncodedImageData.swift: -------------------------------------------------------------------------------- 1 | /// 2 | /// Func_void_EncodedImageData.swift 3 | /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. 4 | /// https://github.com/mrousavy/nitro 5 | /// Copyright © 2025 Marc Rousavy @ Margelo 6 | /// 7 | 8 | import NitroModules 9 | 10 | 11 | /** 12 | * Wraps a Swift `(_ value: EncodedImageData) -> Void` as a class. 13 | * This class can be used from C++, e.g. to wrap the Swift closure as a `std::function`. 14 | */ 15 | public final class Func_void_EncodedImageData { 16 | public typealias bridge = margelo.nitro.image.bridge.swift 17 | 18 | private let closure: (_ value: EncodedImageData) -> Void 19 | 20 | public init(_ closure: @escaping (_ value: EncodedImageData) -> Void) { 21 | self.closure = closure 22 | } 23 | 24 | @inline(__always) 25 | public func call(value: EncodedImageData) -> Void { 26 | self.closure(value) 27 | } 28 | 29 | /** 30 | * Casts this instance to a retained unsafe raw pointer. 31 | * This acquires one additional strong reference on the object! 32 | */ 33 | @inline(__always) 34 | public func toUnsafe() -> UnsafeMutableRawPointer { 35 | return Unmanaged.passRetained(self).toOpaque() 36 | } 37 | 38 | /** 39 | * Casts an unsafe pointer to a `Func_void_EncodedImageData`. 40 | * The pointer has to be a retained opaque `Unmanaged`. 41 | * This removes one strong reference from the object! 42 | */ 43 | @inline(__always) 44 | public static func fromUnsafe(_ pointer: UnsafeMutableRawPointer) -> Func_void_EncodedImageData { 45 | return Unmanaged.fromOpaque(pointer).takeRetainedValue() 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /packages/react-native-nitro-image/nitrogen/generated/ios/swift/Func_void_std__exception_ptr.swift: -------------------------------------------------------------------------------- 1 | /// 2 | /// Func_void_std__exception_ptr.swift 3 | /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. 4 | /// https://github.com/mrousavy/nitro 5 | /// Copyright © 2025 Marc Rousavy @ Margelo 6 | /// 7 | 8 | import NitroModules 9 | 10 | 11 | /** 12 | * Wraps a Swift `(_ error: Error) -> Void` as a class. 13 | * This class can be used from C++, e.g. to wrap the Swift closure as a `std::function`. 14 | */ 15 | public final class Func_void_std__exception_ptr { 16 | public typealias bridge = margelo.nitro.image.bridge.swift 17 | 18 | private let closure: (_ error: Error) -> Void 19 | 20 | public init(_ closure: @escaping (_ error: Error) -> Void) { 21 | self.closure = closure 22 | } 23 | 24 | @inline(__always) 25 | public func call(error: std.exception_ptr) -> Void { 26 | self.closure(RuntimeError.from(cppError: error)) 27 | } 28 | 29 | /** 30 | * Casts this instance to a retained unsafe raw pointer. 31 | * This acquires one additional strong reference on the object! 32 | */ 33 | @inline(__always) 34 | public func toUnsafe() -> UnsafeMutableRawPointer { 35 | return Unmanaged.passRetained(self).toOpaque() 36 | } 37 | 38 | /** 39 | * Casts an unsafe pointer to a `Func_void_std__exception_ptr`. 40 | * The pointer has to be a retained opaque `Unmanaged`. 41 | * This removes one strong reference from the object! 42 | */ 43 | @inline(__always) 44 | public static func fromUnsafe(_ pointer: UnsafeMutableRawPointer) -> Func_void_std__exception_ptr { 45 | return Unmanaged.fromOpaque(pointer).takeRetainedValue() 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /packages/react-native-nitro-image/ios/UIImage+getData.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIImage+getData.swift 3 | // react-native-nitro-image 4 | // 5 | // Created by Marc Rousavy on 11.06.25. 6 | // 7 | 8 | import UIKit 9 | import NitroModules 10 | 11 | extension UIImage { 12 | /** 13 | * Convert/Compress this Image into the given `format`. 14 | * `quality` specifies compression quality from 0(most)...100(least). 15 | */ 16 | func getData(in format: ImageFormat, quality: CGFloat) throws -> Data { 17 | switch format { 18 | case .jpg: 19 | guard quality >= 0 && quality <= 100 else { 20 | throw RuntimeError.error(withMessage: "Image quality has to be between 0 and 100! (Received: \(quality))") 21 | } 22 | let qualityNormalized = quality / 100.0 23 | guard let data = self.jpegData(compressionQuality: qualityNormalized) else { 24 | throw RuntimeError.error(withMessage: "Failed to compress \(size.width)x\(size.height) Image to JPEG! (Quality: \(quality))") 25 | } 26 | return data 27 | case .png: 28 | guard let data = self.pngData() else { 29 | throw RuntimeError.error(withMessage: "Failed to convert \(size.width)x\(size.height) Image to PNG!") 30 | } 31 | return data 32 | case .heic: 33 | guard #available(iOS 17.0, *) else { 34 | throw RuntimeError.error(withMessage: "HEIC writing is only available on iOS 17.0 or higher! " + 35 | "Check ImageUtils.supportsHeicWriting before calling this method.") 36 | } 37 | guard let data = self.heicData() else { 38 | throw RuntimeError.error(withMessage: "Failed to convert \(size.width)x\(size.height) Image to HEIC!") 39 | } 40 | return data 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /packages/react-native-nitro-image/nitrogen/generated/ios/swift/EncodedImageData.swift: -------------------------------------------------------------------------------- 1 | /// 2 | /// EncodedImageData.swift 3 | /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. 4 | /// https://github.com/mrousavy/nitro 5 | /// Copyright © 2025 Marc Rousavy @ Margelo 6 | /// 7 | 8 | import NitroModules 9 | 10 | /** 11 | * Represents an instance of `EncodedImageData`, backed by a C++ struct. 12 | */ 13 | public typealias EncodedImageData = margelo.nitro.image.EncodedImageData 14 | 15 | public extension EncodedImageData { 16 | private typealias bridge = margelo.nitro.image.bridge.swift 17 | 18 | /** 19 | * Create a new instance of `EncodedImageData`. 20 | */ 21 | init(buffer: ArrayBuffer, width: Double, height: Double, imageFormat: ImageFormat) { 22 | self.init(buffer.getArrayBuffer(), width, height, imageFormat) 23 | } 24 | 25 | var buffer: ArrayBuffer { 26 | @inline(__always) 27 | get { 28 | return ArrayBuffer(self.__buffer) 29 | } 30 | @inline(__always) 31 | set { 32 | self.__buffer = newValue.getArrayBuffer() 33 | } 34 | } 35 | 36 | var width: Double { 37 | @inline(__always) 38 | get { 39 | return self.__width 40 | } 41 | @inline(__always) 42 | set { 43 | self.__width = newValue 44 | } 45 | } 46 | 47 | var height: Double { 48 | @inline(__always) 49 | get { 50 | return self.__height 51 | } 52 | @inline(__always) 53 | set { 54 | self.__height = newValue 55 | } 56 | } 57 | 58 | var imageFormat: ImageFormat { 59 | @inline(__always) 60 | get { 61 | return self.__imageFormat 62 | } 63 | @inline(__always) 64 | set { 65 | self.__imageFormat = newValue 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /packages/react-native-nitro-web-image/nitrogen/generated/ios/swift/Func_void_std__exception_ptr.swift: -------------------------------------------------------------------------------- 1 | /// 2 | /// Func_void_std__exception_ptr.swift 3 | /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. 4 | /// https://github.com/mrousavy/nitro 5 | /// Copyright © 2025 Marc Rousavy @ Margelo 6 | /// 7 | 8 | import NitroModules 9 | 10 | 11 | /** 12 | * Wraps a Swift `(_ error: Error) -> Void` as a class. 13 | * This class can be used from C++, e.g. to wrap the Swift closure as a `std::function`. 14 | */ 15 | public final class Func_void_std__exception_ptr { 16 | public typealias bridge = margelo.nitro.web.image.bridge.swift 17 | 18 | private let closure: (_ error: Error) -> Void 19 | 20 | public init(_ closure: @escaping (_ error: Error) -> Void) { 21 | self.closure = closure 22 | } 23 | 24 | @inline(__always) 25 | public func call(error: std.exception_ptr) -> Void { 26 | self.closure(RuntimeError.from(cppError: error)) 27 | } 28 | 29 | /** 30 | * Casts this instance to a retained unsafe raw pointer. 31 | * This acquires one additional strong reference on the object! 32 | */ 33 | @inline(__always) 34 | public func toUnsafe() -> UnsafeMutableRawPointer { 35 | return Unmanaged.passRetained(self).toOpaque() 36 | } 37 | 38 | /** 39 | * Casts an unsafe pointer to a `Func_void_std__exception_ptr`. 40 | * The pointer has to be a retained opaque `Unmanaged`. 41 | * This removes one strong reference from the object! 42 | */ 43 | @inline(__always) 44 | public static func fromUnsafe(_ pointer: UnsafeMutableRawPointer) -> Func_void_std__exception_ptr { 45 | return Unmanaged.fromOpaque(pointer).takeRetainedValue() 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "NitroImageExample", 3 | "version": "0.9.0", 4 | "private": true, 5 | "scripts": { 6 | "android": "react-native run-android", 7 | "ios": "react-native run-ios", 8 | "lint": "biome check .", 9 | "start": "react-native start --client-logs", 10 | "pods": "bundle install && cd ios && bundle exec pod install", 11 | "bundle-install": "bundle install", 12 | "build:android-release": "cd android && ./gradlew assembleRelease --no-daemon" 13 | }, 14 | "dependencies": { 15 | "@react-navigation/bottom-tabs": "^7.4.6", 16 | "@react-navigation/native": "^7.1.17", 17 | "react": "19.1.0", 18 | "react-native": "0.81.0", 19 | "react-native-fast-image": "^8.6.3", 20 | "react-native-nitro-image": "../packages/react-native-nitro-image", 21 | "react-native-nitro-web-image": "../packages/react-native-nitro-web-image", 22 | "react-native-nitro-modules": "0.31.5", 23 | "react-native-safe-area-context": "^5.6.0", 24 | "react-native-screens": "^4.14.1" 25 | }, 26 | "devDependencies": { 27 | "@babel/core": "^7.27.4", 28 | "@babel/preset-env": "^7.27.2", 29 | "@babel/runtime": "^7.27.6", 30 | "@react-native-community/cli": "20.0.0", 31 | "@react-native-community/cli-platform-android": "20.0.0", 32 | "@react-native-community/cli-platform-ios": "20.0.0", 33 | "@react-native/babel-preset": "0.81.0", 34 | "@react-native/metro-config": "0.81.0", 35 | "@react-native/typescript-config": "0.81.0", 36 | "@types/react": "^19.1.0", 37 | "typescript": "5.8.3" 38 | }, 39 | "engines": { 40 | "node": ">=18" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /packages/react-native-nitro-image/nitrogen/generated/ios/swift/Func_void_std__shared_ptr_ArrayBuffer_.swift: -------------------------------------------------------------------------------- 1 | /// 2 | /// Func_void_std__shared_ptr_ArrayBuffer_.swift 3 | /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. 4 | /// https://github.com/mrousavy/nitro 5 | /// Copyright © 2025 Marc Rousavy @ Margelo 6 | /// 7 | 8 | import NitroModules 9 | import NitroModules 10 | 11 | /** 12 | * Wraps a Swift `(_ value: ArrayBuffer) -> Void` as a class. 13 | * This class can be used from C++, e.g. to wrap the Swift closure as a `std::function`. 14 | */ 15 | public final class Func_void_std__shared_ptr_ArrayBuffer_ { 16 | public typealias bridge = margelo.nitro.image.bridge.swift 17 | 18 | private let closure: (_ value: ArrayBuffer) -> Void 19 | 20 | public init(_ closure: @escaping (_ value: ArrayBuffer) -> Void) { 21 | self.closure = closure 22 | } 23 | 24 | @inline(__always) 25 | public func call(value: ArrayBuffer) -> Void { 26 | self.closure(value) 27 | } 28 | 29 | /** 30 | * Casts this instance to a retained unsafe raw pointer. 31 | * This acquires one additional strong reference on the object! 32 | */ 33 | @inline(__always) 34 | public func toUnsafe() -> UnsafeMutableRawPointer { 35 | return Unmanaged.passRetained(self).toOpaque() 36 | } 37 | 38 | /** 39 | * Casts an unsafe pointer to a `Func_void_std__shared_ptr_ArrayBuffer_`. 40 | * The pointer has to be a retained opaque `Unmanaged`. 41 | * This removes one strong reference from the object! 42 | */ 43 | @inline(__always) 44 | public static func fromUnsafe(_ pointer: UnsafeMutableRawPointer) -> Func_void_std__shared_ptr_ArrayBuffer_ { 45 | return Unmanaged.fromOpaque(pointer).takeRetainedValue() 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /example/ios/NitroImageExample/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | NitroImageExample 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(MARKETING_VERSION) 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | $(CURRENT_PROJECT_VERSION) 25 | LSRequiresIPhoneOS 26 | 27 | NSAppTransportSecurity 28 | 29 | NSAllowsArbitraryLoads 30 | 31 | NSAllowsLocalNetworking 32 | 33 | 34 | NSLocationWhenInUseUsageDescription 35 | 36 | RCTNewArchEnabled 37 | 38 | UILaunchStoryboardName 39 | LaunchScreen 40 | UIRequiredDeviceCapabilities 41 | 42 | arm64 43 | 44 | UISupportedInterfaceOrientations 45 | 46 | UIInterfaceOrientationPortrait 47 | UIInterfaceOrientationLandscapeLeft 48 | UIInterfaceOrientationLandscapeRight 49 | 50 | UIViewControllerBasedStatusBarAppearance 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /packages/react-native-nitro-image/nitrogen/generated/android/kotlin/com/margelo/nitro/image/HybridImageLoaderSpec.kt: -------------------------------------------------------------------------------- 1 | /// 2 | /// HybridImageLoaderSpec.kt 3 | /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. 4 | /// https://github.com/mrousavy/nitro 5 | /// Copyright © 2025 Marc Rousavy @ Margelo 6 | /// 7 | 8 | package com.margelo.nitro.image 9 | 10 | import androidx.annotation.Keep 11 | import com.facebook.jni.HybridData 12 | import com.facebook.proguard.annotations.DoNotStrip 13 | import com.margelo.nitro.core.Promise 14 | import com.margelo.nitro.core.HybridObject 15 | 16 | /** 17 | * A Kotlin class representing the ImageLoader HybridObject. 18 | * Implement this abstract class to create Kotlin-based instances of ImageLoader. 19 | */ 20 | @DoNotStrip 21 | @Keep 22 | @Suppress( 23 | "KotlinJniMissingFunction", "unused", 24 | "RedundantSuppression", "RedundantUnitReturnType", "SimpleRedundantLet", 25 | "LocalVariableName", "PropertyName", "PrivatePropertyName", "FunctionName" 26 | ) 27 | abstract class HybridImageLoaderSpec: HybridObject() { 28 | @DoNotStrip 29 | private var mHybridData: HybridData = initHybrid() 30 | 31 | init { 32 | super.updateNative(mHybridData) 33 | } 34 | 35 | override fun updateNative(hybridData: HybridData) { 36 | mHybridData = hybridData 37 | super.updateNative(hybridData) 38 | } 39 | 40 | // Properties 41 | 42 | 43 | // Methods 44 | @DoNotStrip 45 | @Keep 46 | abstract fun loadImage(): Promise 47 | 48 | @DoNotStrip 49 | @Keep 50 | abstract fun requestImage(forView: HybridNitroImageViewSpec): Unit 51 | 52 | @DoNotStrip 53 | @Keep 54 | abstract fun dropImage(forView: HybridNitroImageViewSpec): Unit 55 | 56 | private external fun initHybrid(): HybridData 57 | 58 | companion object { 59 | protected const val TAG = "HybridImageLoaderSpec" 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /.github/workflows/build-android.yml: -------------------------------------------------------------------------------- 1 | name: Build Android 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | paths: 8 | - '.github/workflows/build-android.yml' 9 | - 'example/android/**' 10 | - '**/nitrogen/generated/shared/**' 11 | - '**/nitrogen/generated/android/**' 12 | - 'android/**' 13 | - '**/bun.lock' 14 | - '**/react-native.config.js' 15 | - '**/nitro.json' 16 | pull_request: 17 | paths: 18 | - '.github/workflows/build-android.yml' 19 | - 'example/android/**' 20 | - '**/nitrogen/generated/shared/**' 21 | - '**/nitrogen/generated/android/**' 22 | - 'android/**' 23 | - '**/bun.lock' 24 | - '**/react-native.config.js' 25 | - '**/nitro.json' 26 | 27 | jobs: 28 | build: 29 | name: Build Android Example App 30 | runs-on: ubuntu-latest 31 | steps: 32 | - uses: actions/checkout@v4 33 | - uses: oven-sh/setup-bun@v2 34 | 35 | - name: Install npm dependencies (bun) 36 | run: bun install 37 | 38 | - name: Setup JDK 17 39 | uses: actions/setup-java@v4 40 | with: 41 | distribution: 'zulu' 42 | java-version: 17 43 | java-package: jdk 44 | 45 | - name: Restore Gradle cache 46 | uses: actions/cache@v4 47 | with: 48 | path: | 49 | ~/.gradle/caches 50 | key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} 51 | restore-keys: | 52 | ${{ runner.os }}-gradle- 53 | - name: Run Gradle Build for example/android/ 54 | working-directory: example/android 55 | run: ./gradlew assembleDebug --no-daemon --build-cache 56 | 57 | # Gradle cache doesn't like daemons 58 | - name: Stop Gradle Daemon 59 | working-directory: example/android 60 | run: ./gradlew --stop 61 | -------------------------------------------------------------------------------- /packages/react-native-nitro-image/nitrogen/generated/ios/swift/HybridImageLoaderSpec.swift: -------------------------------------------------------------------------------- 1 | /// 2 | /// HybridImageLoaderSpec.swift 3 | /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. 4 | /// https://github.com/mrousavy/nitro 5 | /// Copyright © 2025 Marc Rousavy @ Margelo 6 | /// 7 | 8 | import Foundation 9 | import NitroModules 10 | import NitroModules 11 | 12 | /// See ``HybridImageLoaderSpec`` 13 | public protocol HybridImageLoaderSpec_protocol: HybridObject { 14 | // Properties 15 | 16 | 17 | // Methods 18 | func loadImage() throws -> Promise<(any HybridImageSpec)> 19 | func requestImage(forView: (any HybridNitroImageViewSpec)) throws -> Void 20 | func dropImage(forView: (any HybridNitroImageViewSpec)) throws -> Void 21 | } 22 | 23 | /// See ``HybridImageLoaderSpec`` 24 | open class HybridImageLoaderSpec_base { 25 | private weak var cxxWrapper: HybridImageLoaderSpec_cxx? = nil 26 | public init() { } 27 | public func getCxxWrapper() -> HybridImageLoaderSpec_cxx { 28 | #if DEBUG 29 | guard self is HybridImageLoaderSpec else { 30 | fatalError("`self` is not a `HybridImageLoaderSpec`! Did you accidentally inherit from `HybridImageLoaderSpec_base` instead of `HybridImageLoaderSpec`?") 31 | } 32 | #endif 33 | if let cxxWrapper = self.cxxWrapper { 34 | return cxxWrapper 35 | } else { 36 | let cxxWrapper = HybridImageLoaderSpec_cxx(self as! HybridImageLoaderSpec) 37 | self.cxxWrapper = cxxWrapper 38 | return cxxWrapper 39 | } 40 | } 41 | } 42 | 43 | /** 44 | * A Swift base-protocol representing the ImageLoader HybridObject. 45 | * Implement this protocol to create Swift-based instances of ImageLoader. 46 | * ```swift 47 | * class HybridImageLoader : HybridImageLoaderSpec { 48 | * // ... 49 | * } 50 | * ``` 51 | */ 52 | public typealias HybridImageLoaderSpec = HybridImageLoaderSpec_protocol & HybridImageLoaderSpec_base 53 | -------------------------------------------------------------------------------- /packages/react-native-nitro-image/nitrogen/generated/ios/swift/HybridImageUtilsSpec.swift: -------------------------------------------------------------------------------- 1 | /// 2 | /// HybridImageUtilsSpec.swift 3 | /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. 4 | /// https://github.com/mrousavy/nitro 5 | /// Copyright © 2025 Marc Rousavy @ Margelo 6 | /// 7 | 8 | import Foundation 9 | import NitroModules 10 | import NitroModules 11 | 12 | /// See ``HybridImageUtilsSpec`` 13 | public protocol HybridImageUtilsSpec_protocol: HybridObject { 14 | // Properties 15 | var supportsHeicLoading: Bool { get } 16 | var supportsHeicWriting: Bool { get } 17 | 18 | // Methods 19 | func thumbHashToBase64String(thumbhash: ArrayBuffer) throws -> String 20 | func thumbhashFromBase64String(thumbhashBase64: String) throws -> ArrayBuffer 21 | } 22 | 23 | /// See ``HybridImageUtilsSpec`` 24 | open class HybridImageUtilsSpec_base { 25 | private weak var cxxWrapper: HybridImageUtilsSpec_cxx? = nil 26 | public init() { } 27 | public func getCxxWrapper() -> HybridImageUtilsSpec_cxx { 28 | #if DEBUG 29 | guard self is HybridImageUtilsSpec else { 30 | fatalError("`self` is not a `HybridImageUtilsSpec`! Did you accidentally inherit from `HybridImageUtilsSpec_base` instead of `HybridImageUtilsSpec`?") 31 | } 32 | #endif 33 | if let cxxWrapper = self.cxxWrapper { 34 | return cxxWrapper 35 | } else { 36 | let cxxWrapper = HybridImageUtilsSpec_cxx(self as! HybridImageUtilsSpec) 37 | self.cxxWrapper = cxxWrapper 38 | return cxxWrapper 39 | } 40 | } 41 | } 42 | 43 | /** 44 | * A Swift base-protocol representing the ImageUtils HybridObject. 45 | * Implement this protocol to create Swift-based instances of ImageUtils. 46 | * ```swift 47 | * class HybridImageUtils : HybridImageUtilsSpec { 48 | * // ... 49 | * } 50 | * ``` 51 | */ 52 | public typealias HybridImageUtilsSpec = HybridImageUtilsSpec_protocol & HybridImageUtilsSpec_base 53 | -------------------------------------------------------------------------------- /packages/react-native-nitro-image/nitrogen/generated/android/kotlin/com/margelo/nitro/image/views/HybridNitroImageViewManager.kt: -------------------------------------------------------------------------------- 1 | /// 2 | /// HybridNitroImageViewManager.kt 3 | /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. 4 | /// https://github.com/mrousavy/nitro 5 | /// Copyright © 2025 Marc Rousavy @ Margelo 6 | /// 7 | 8 | package com.margelo.nitro.image.views 9 | 10 | import android.view.View 11 | import com.facebook.react.uimanager.ReactStylesDiffMap 12 | import com.facebook.react.uimanager.SimpleViewManager 13 | import com.facebook.react.uimanager.StateWrapper 14 | import com.facebook.react.uimanager.ThemedReactContext 15 | import com.margelo.nitro.image.* 16 | 17 | /** 18 | * Represents the React Native `ViewManager` for the "NitroImageView" Nitro HybridView. 19 | */ 20 | open class HybridNitroImageViewManager: SimpleViewManager() { 21 | private val views = hashMapOf() 22 | 23 | override fun getName(): String { 24 | return "NitroImageView" 25 | } 26 | 27 | override fun createViewInstance(reactContext: ThemedReactContext): View { 28 | val hybridView = HybridImageView(reactContext) 29 | val view = hybridView.view 30 | views[view] = hybridView 31 | return view 32 | } 33 | 34 | override fun onDropViewInstance(view: View) { 35 | super.onDropViewInstance(view) 36 | views.remove(view) 37 | } 38 | 39 | override fun updateState(view: View, props: ReactStylesDiffMap, stateWrapper: StateWrapper): Any? { 40 | val hybridView = views[view] ?: throw Error("Couldn't find view $view in local views table!") 41 | 42 | // 1. Update each prop individually 43 | hybridView.beforeUpdate() 44 | HybridNitroImageViewStateUpdater.updateViewProps(hybridView, stateWrapper) 45 | hybridView.afterUpdate() 46 | 47 | // 2. Continue in base View props 48 | return super.updateState(view, props, stateWrapper) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /packages/react-native-nitro-image/android/src/main/java/com/margelo/nitro/image/Bitmap+pixelFormat.kt: -------------------------------------------------------------------------------- 1 | package com.margelo.nitro.image 2 | 3 | import android.graphics.Bitmap 4 | import android.hardware.HardwareBuffer 5 | import android.os.Build 6 | import java.nio.ByteOrder 7 | 8 | val Bitmap.pixelFormat: PixelFormat 9 | get() { 10 | when (config) { 11 | Bitmap.Config.ARGB_8888 -> { 12 | // On Android, ARGB_8888 defines memory layout inside an Int, not the byte order. 13 | // So where iOS would define Pixel Format ARGB as byte order [A, R, G, B], 14 | // Android instead defines the Pixel Format ARGB as 0xAARRGGBB (32-bit Int) - 15 | // which - if read on a little-endian machine, is reversed ([B, G, R, A]). 16 | if (ByteOrder.nativeOrder() == ByteOrder.LITTLE_ENDIAN) { 17 | // almost every device nowadays is little endian 18 | return PixelFormat.BGRA 19 | } else { 20 | // no devices use big endian anymore, but we keep this to highlight 21 | // why ARGB becomes BGRA on little endian. 22 | return PixelFormat.ARGB 23 | } 24 | } 25 | Bitmap.Config.HARDWARE -> { 26 | // Hardware Buffer is either RGBA or RGBX 27 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { 28 | when (hardwareBuffer.format) { 29 | HardwareBuffer.RGBA_8888 -> return PixelFormat.RGBA 30 | HardwareBuffer.RGBX_8888 -> return PixelFormat.RGBX 31 | HardwareBuffer.RGB_888 -> return PixelFormat.RGB 32 | } 33 | } 34 | return PixelFormat.UNKNOWN 35 | } 36 | else -> return PixelFormat.UNKNOWN 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /packages/react-native-nitro-image/nitrogen/generated/ios/swift/HybridNitroImageViewSpec.swift: -------------------------------------------------------------------------------- 1 | /// 2 | /// HybridNitroImageViewSpec.swift 3 | /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. 4 | /// https://github.com/mrousavy/nitro 5 | /// Copyright © 2025 Marc Rousavy @ Margelo 6 | /// 7 | 8 | import Foundation 9 | import NitroModules 10 | 11 | /// See ``HybridNitroImageViewSpec`` 12 | public protocol HybridNitroImageViewSpec_protocol: HybridObject, HybridView { 13 | // Properties 14 | var image: Variant__any_HybridImageSpec___any_HybridImageLoaderSpec_? { get set } 15 | var resizeMode: ResizeMode? { get set } 16 | var recyclingKey: String? { get set } 17 | 18 | // Methods 19 | 20 | } 21 | 22 | /// See ``HybridNitroImageViewSpec`` 23 | open class HybridNitroImageViewSpec_base { 24 | private weak var cxxWrapper: HybridNitroImageViewSpec_cxx? = nil 25 | public init() { } 26 | public func getCxxWrapper() -> HybridNitroImageViewSpec_cxx { 27 | #if DEBUG 28 | guard self is HybridNitroImageViewSpec else { 29 | fatalError("`self` is not a `HybridNitroImageViewSpec`! Did you accidentally inherit from `HybridNitroImageViewSpec_base` instead of `HybridNitroImageViewSpec`?") 30 | } 31 | #endif 32 | if let cxxWrapper = self.cxxWrapper { 33 | return cxxWrapper 34 | } else { 35 | let cxxWrapper = HybridNitroImageViewSpec_cxx(self as! HybridNitroImageViewSpec) 36 | self.cxxWrapper = cxxWrapper 37 | return cxxWrapper 38 | } 39 | } 40 | } 41 | 42 | /** 43 | * A Swift base-protocol representing the NitroImageView HybridObject. 44 | * Implement this protocol to create Swift-based instances of NitroImageView. 45 | * ```swift 46 | * class HybridNitroImageView : HybridNitroImageViewSpec { 47 | * // ... 48 | * } 49 | * ``` 50 | */ 51 | public typealias HybridNitroImageViewSpec = HybridNitroImageViewSpec_protocol & HybridNitroImageViewSpec_base 52 | -------------------------------------------------------------------------------- /packages/react-native-nitro-image/nitrogen/generated/android/kotlin/com/margelo/nitro/image/HybridNitroImageViewSpec.kt: -------------------------------------------------------------------------------- 1 | /// 2 | /// HybridNitroImageViewSpec.kt 3 | /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. 4 | /// https://github.com/mrousavy/nitro 5 | /// Copyright © 2025 Marc Rousavy @ Margelo 6 | /// 7 | 8 | package com.margelo.nitro.image 9 | 10 | import androidx.annotation.Keep 11 | import com.facebook.jni.HybridData 12 | import com.facebook.proguard.annotations.DoNotStrip 13 | import com.margelo.nitro.views.HybridView 14 | 15 | /** 16 | * A Kotlin class representing the NitroImageView HybridObject. 17 | * Implement this abstract class to create Kotlin-based instances of NitroImageView. 18 | */ 19 | @DoNotStrip 20 | @Keep 21 | @Suppress( 22 | "KotlinJniMissingFunction", "unused", 23 | "RedundantSuppression", "RedundantUnitReturnType", "SimpleRedundantLet", 24 | "LocalVariableName", "PropertyName", "PrivatePropertyName", "FunctionName" 25 | ) 26 | abstract class HybridNitroImageViewSpec: HybridView() { 27 | @DoNotStrip 28 | private var mHybridData: HybridData = initHybrid() 29 | 30 | init { 31 | super.updateNative(mHybridData) 32 | } 33 | 34 | override fun updateNative(hybridData: HybridData) { 35 | mHybridData = hybridData 36 | super.updateNative(hybridData) 37 | } 38 | 39 | // Properties 40 | @get:DoNotStrip 41 | @get:Keep 42 | @set:DoNotStrip 43 | @set:Keep 44 | abstract var image: Variant_HybridImageSpec_HybridImageLoaderSpec? 45 | 46 | @get:DoNotStrip 47 | @get:Keep 48 | @set:DoNotStrip 49 | @set:Keep 50 | abstract var resizeMode: ResizeMode? 51 | 52 | @get:DoNotStrip 53 | @get:Keep 54 | @set:DoNotStrip 55 | @set:Keep 56 | abstract var recyclingKey: String? 57 | 58 | // Methods 59 | 60 | 61 | private external fun initHybrid(): HybridData 62 | 63 | companion object { 64 | protected const val TAG = "HybridNitroImageViewSpec" 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /packages/react-native-nitro-image/nitrogen/generated/ios/swift/Color.swift: -------------------------------------------------------------------------------- 1 | /// 2 | /// Color.swift 3 | /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. 4 | /// https://github.com/mrousavy/nitro 5 | /// Copyright © 2025 Marc Rousavy @ Margelo 6 | /// 7 | 8 | import NitroModules 9 | 10 | /** 11 | * Represents an instance of `Color`, backed by a C++ struct. 12 | */ 13 | public typealias Color = margelo.nitro.image.Color 14 | 15 | public extension Color { 16 | private typealias bridge = margelo.nitro.image.bridge.swift 17 | 18 | /** 19 | * Create a new instance of `Color`. 20 | */ 21 | init(r: Double, g: Double, b: Double, a: Double?) { 22 | self.init(r, g, b, { () -> bridge.std__optional_double_ in 23 | if let __unwrappedValue = a { 24 | return bridge.create_std__optional_double_(__unwrappedValue) 25 | } else { 26 | return .init() 27 | } 28 | }()) 29 | } 30 | 31 | var r: Double { 32 | @inline(__always) 33 | get { 34 | return self.__r 35 | } 36 | @inline(__always) 37 | set { 38 | self.__r = newValue 39 | } 40 | } 41 | 42 | var g: Double { 43 | @inline(__always) 44 | get { 45 | return self.__g 46 | } 47 | @inline(__always) 48 | set { 49 | self.__g = newValue 50 | } 51 | } 52 | 53 | var b: Double { 54 | @inline(__always) 55 | get { 56 | return self.__b 57 | } 58 | @inline(__always) 59 | set { 60 | self.__b = newValue 61 | } 62 | } 63 | 64 | var a: Double? { 65 | @inline(__always) 66 | get { 67 | return self.__a.value 68 | } 69 | @inline(__always) 70 | set { 71 | self.__a = { () -> bridge.std__optional_double_ in 72 | if let __unwrappedValue = newValue { 73 | return bridge.create_std__optional_double_(__unwrappedValue) 74 | } else { 75 | return .init() 76 | } 77 | }() 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /packages/react-native-nitro-image/src/useImage.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | import { type AsyncImageSource, isHybridObject } from "./AsyncImageSource"; 3 | import { loadImage } from "./loadImage"; 4 | import { markHybridObject } from "./markHybridObject"; 5 | import type { Image } from "./specs/Image.nitro"; 6 | 7 | type Result = 8 | // Loading State 9 | | { 10 | image: undefined; 11 | error: undefined; 12 | } 13 | // Loaded state 14 | | { 15 | image: Image; 16 | error: undefined; 17 | } 18 | // Error state 19 | | { 20 | image: undefined; 21 | error: Error; 22 | }; 23 | 24 | /** 25 | * A hook to asynchronously load an image from the 26 | * given {@linkcode AsyncImageSource} into memory. 27 | * @example 28 | * ```ts 29 | * const { image, error } = useImage({ filePath: '/tmp/image.jpg' }) 30 | * ``` 31 | */ 32 | export function useImage(source: AsyncImageSource): Result { 33 | const [image, setImage] = useState({ 34 | image: undefined, 35 | error: undefined, 36 | }); 37 | 38 | // biome-ignore lint: The dependencies array is a bit hacky. 39 | useEffect(() => { 40 | (async () => { 41 | try { 42 | // 1. Create the Image/ImageLoader instance 43 | const result = await loadImage(source); 44 | // 2. Add `__source` as a property on the JS side so React diffs properly 45 | markHybridObject(result, source); 46 | // 3. Update the state 47 | setImage({ image: result, error: undefined }); 48 | } catch (e) { 49 | const error = e instanceof Error ? e : new Error(`${e}`); 50 | setImage({ image: undefined, error: error }); 51 | } 52 | })(); 53 | }, [isHybridObject(source) ? source : JSON.stringify(source)]); 54 | 55 | return image; 56 | } 57 | -------------------------------------------------------------------------------- /packages/react-native-nitro-image/nitrogen/generated/android/kotlin/com/margelo/nitro/image/HybridImageUtilsSpec.kt: -------------------------------------------------------------------------------- 1 | /// 2 | /// HybridImageUtilsSpec.kt 3 | /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. 4 | /// https://github.com/mrousavy/nitro 5 | /// Copyright © 2025 Marc Rousavy @ Margelo 6 | /// 7 | 8 | package com.margelo.nitro.image 9 | 10 | import androidx.annotation.Keep 11 | import com.facebook.jni.HybridData 12 | import com.facebook.proguard.annotations.DoNotStrip 13 | import com.margelo.nitro.core.ArrayBuffer 14 | import com.margelo.nitro.core.HybridObject 15 | 16 | /** 17 | * A Kotlin class representing the ImageUtils HybridObject. 18 | * Implement this abstract class to create Kotlin-based instances of ImageUtils. 19 | */ 20 | @DoNotStrip 21 | @Keep 22 | @Suppress( 23 | "KotlinJniMissingFunction", "unused", 24 | "RedundantSuppression", "RedundantUnitReturnType", "SimpleRedundantLet", 25 | "LocalVariableName", "PropertyName", "PrivatePropertyName", "FunctionName" 26 | ) 27 | abstract class HybridImageUtilsSpec: HybridObject() { 28 | @DoNotStrip 29 | private var mHybridData: HybridData = initHybrid() 30 | 31 | init { 32 | super.updateNative(mHybridData) 33 | } 34 | 35 | override fun updateNative(hybridData: HybridData) { 36 | mHybridData = hybridData 37 | super.updateNative(hybridData) 38 | } 39 | 40 | // Properties 41 | @get:DoNotStrip 42 | @get:Keep 43 | abstract val supportsHeicLoading: Boolean 44 | 45 | @get:DoNotStrip 46 | @get:Keep 47 | abstract val supportsHeicWriting: Boolean 48 | 49 | // Methods 50 | @DoNotStrip 51 | @Keep 52 | abstract fun thumbHashToBase64String(thumbhash: ArrayBuffer): String 53 | 54 | @DoNotStrip 55 | @Keep 56 | abstract fun thumbhashFromBase64String(thumbhashBase64: String): ArrayBuffer 57 | 58 | private external fun initHybrid(): HybridData 59 | 60 | companion object { 61 | protected const val TAG = "HybridImageUtilsSpec" 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /packages/react-native-nitro-web-image/nitrogen/generated/android/kotlin/com/margelo/nitro/web/image/AsyncImageLoadOptions.kt: -------------------------------------------------------------------------------- 1 | /// 2 | /// AsyncImageLoadOptions.kt 3 | /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. 4 | /// https://github.com/mrousavy/nitro 5 | /// Copyright © 2025 Marc Rousavy @ Margelo 6 | /// 7 | 8 | package com.margelo.nitro.web.image 9 | 10 | import androidx.annotation.Keep 11 | import com.facebook.proguard.annotations.DoNotStrip 12 | 13 | 14 | /** 15 | * Represents the JavaScript object/struct "AsyncImageLoadOptions". 16 | */ 17 | @DoNotStrip 18 | @Keep 19 | data class AsyncImageLoadOptions( 20 | @DoNotStrip 21 | @Keep 22 | val priority: AsyncImagePriority?, 23 | @DoNotStrip 24 | @Keep 25 | val forceRefresh: Boolean?, 26 | @DoNotStrip 27 | @Keep 28 | val cacheKey: String?, 29 | @DoNotStrip 30 | @Keep 31 | val continueInBackground: Boolean?, 32 | @DoNotStrip 33 | @Keep 34 | val allowInvalidSSLCertificates: Boolean?, 35 | @DoNotStrip 36 | @Keep 37 | val scaleDownLargeImages: Boolean?, 38 | @DoNotStrip 39 | @Keep 40 | val queryMemoryDataSync: Boolean?, 41 | @DoNotStrip 42 | @Keep 43 | val queryDiskDataSync: Boolean?, 44 | @DoNotStrip 45 | @Keep 46 | val decodeImage: Boolean? 47 | ) { 48 | private companion object { 49 | /** 50 | * Constructor called from C++ 51 | */ 52 | @DoNotStrip 53 | @Keep 54 | @Suppress("unused") 55 | @JvmStatic 56 | private fun fromCpp(priority: AsyncImagePriority?, forceRefresh: Boolean?, cacheKey: String?, continueInBackground: Boolean?, allowInvalidSSLCertificates: Boolean?, scaleDownLargeImages: Boolean?, queryMemoryDataSync: Boolean?, queryDiskDataSync: Boolean?, decodeImage: Boolean?): AsyncImageLoadOptions { 57 | return AsyncImageLoadOptions(priority, forceRefresh, cacheKey, continueInBackground, allowInvalidSSLCertificates, scaleDownLargeImages, queryMemoryDataSync, queryDiskDataSync, decodeImage) 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /packages/react-native-nitro-image/nitrogen/generated/ios/swift/PixelFormat.swift: -------------------------------------------------------------------------------- 1 | /// 2 | /// PixelFormat.swift 3 | /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. 4 | /// https://github.com/mrousavy/nitro 5 | /// Copyright © 2025 Marc Rousavy @ Margelo 6 | /// 7 | 8 | /** 9 | * Represents the JS union `PixelFormat`, backed by a C++ enum. 10 | */ 11 | public typealias PixelFormat = margelo.nitro.image.PixelFormat 12 | 13 | public extension PixelFormat { 14 | /** 15 | * Get a PixelFormat for the given String value, or 16 | * return `nil` if the given value was invalid/unknown. 17 | */ 18 | init?(fromString string: String) { 19 | switch string { 20 | case "ARGB": 21 | self = .argb 22 | case "BGRA": 23 | self = .bgra 24 | case "ABGR": 25 | self = .abgr 26 | case "RGBA": 27 | self = .rgba 28 | case "XRGB": 29 | self = .xrgb 30 | case "BGRX": 31 | self = .bgrx 32 | case "XBGR": 33 | self = .xbgr 34 | case "RGBX": 35 | self = .rgbx 36 | case "RGB": 37 | self = .rgb 38 | case "BGR": 39 | self = .bgr 40 | case "unknown": 41 | self = .unknown 42 | default: 43 | return nil 44 | } 45 | } 46 | 47 | /** 48 | * Get the String value this PixelFormat represents. 49 | */ 50 | var stringValue: String { 51 | switch self { 52 | case .argb: 53 | return "ARGB" 54 | case .bgra: 55 | return "BGRA" 56 | case .abgr: 57 | return "ABGR" 58 | case .rgba: 59 | return "RGBA" 60 | case .xrgb: 61 | return "XRGB" 62 | case .bgrx: 63 | return "BGRX" 64 | case .xbgr: 65 | return "XBGR" 66 | case .rgbx: 67 | return "RGBX" 68 | case .rgb: 69 | return "RGB" 70 | case .bgr: 71 | return "BGR" 72 | case .unknown: 73 | return "unknown" 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /packages/react-native-nitro-web-image/ios/HybridWebImageLoader.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HybridWebImageLoader.swift 3 | // NitroWebImage 4 | // 5 | // Created by Marc Rousavy on 28.07.25. 6 | // 7 | 8 | import NitroModules 9 | import SDWebImage 10 | import NitroImage 11 | 12 | fileprivate class HybridImage: HybridImageSpec, NativeImage { 13 | let uiImage: UIImage 14 | init(uiImage: UIImage) { 15 | self.uiImage = uiImage 16 | super.init() 17 | } 18 | } 19 | 20 | class HybridWebImageLoader: HybridImageLoaderSpec { 21 | private let url: URL 22 | private let options: AsyncImageLoadOptions? 23 | 24 | init(url: URL, options: AsyncImageLoadOptions?) { 25 | self.url = url 26 | self.options = options 27 | super.init() 28 | } 29 | 30 | func loadImage() throws -> Promise<(any HybridImageSpec)> { 31 | return Self.loadImage(url: self.url, options: self.options) 32 | } 33 | 34 | func requestImage(forView view: (any HybridNitroImageViewSpec)) throws { 35 | guard let view = view as? NativeImageView else { throw RuntimeError.error(withMessage: "Invalid view type!") } 36 | 37 | let webImageOptions = options?.toSDWebImageOptions() ?? [] 38 | let webImageContext = options?.toSDWebImageContext() 39 | view.imageView.sd_setImage(with: url, 40 | placeholderImage: view.imageView.image, 41 | options: webImageOptions, 42 | context: webImageContext) 43 | } 44 | 45 | func dropImage(forView view: (any HybridNitroImageViewSpec)) throws { 46 | // TODO: Do we need to reset the image here or not? 47 | } 48 | 49 | public static func loadImage(url: URL, options: AsyncImageLoadOptions?) -> Promise { 50 | return Promise.async { 51 | let uiImage = try await SDWebImageManager.shared.loadImage(with: url, 52 | options: options) 53 | return HybridImage(uiImage: uiImage) 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /packages/react-native-nitro-web-image/nitrogen/generated/ios/swift/HybridWebImageFactorySpec.swift: -------------------------------------------------------------------------------- 1 | /// 2 | /// HybridWebImageFactorySpec.swift 3 | /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. 4 | /// https://github.com/mrousavy/nitro 5 | /// Copyright © 2025 Marc Rousavy @ Margelo 6 | /// 7 | 8 | import Foundation 9 | import NitroModules 10 | import NitroImage 11 | import NitroModules 12 | 13 | /// See ``HybridWebImageFactorySpec`` 14 | public protocol HybridWebImageFactorySpec_protocol: HybridObject { 15 | // Properties 16 | 17 | 18 | // Methods 19 | func createWebImageLoader(url: String, options: AsyncImageLoadOptions?) throws -> (any HybridImageLoaderSpec) 20 | func loadFromURLAsync(url: String, options: AsyncImageLoadOptions?) throws -> Promise<(any HybridImageSpec)> 21 | func preload(url: String) throws -> Void 22 | } 23 | 24 | /// See ``HybridWebImageFactorySpec`` 25 | open class HybridWebImageFactorySpec_base { 26 | private weak var cxxWrapper: HybridWebImageFactorySpec_cxx? = nil 27 | public init() { } 28 | public func getCxxWrapper() -> HybridWebImageFactorySpec_cxx { 29 | #if DEBUG 30 | guard self is HybridWebImageFactorySpec else { 31 | fatalError("`self` is not a `HybridWebImageFactorySpec`! Did you accidentally inherit from `HybridWebImageFactorySpec_base` instead of `HybridWebImageFactorySpec`?") 32 | } 33 | #endif 34 | if let cxxWrapper = self.cxxWrapper { 35 | return cxxWrapper 36 | } else { 37 | let cxxWrapper = HybridWebImageFactorySpec_cxx(self as! HybridWebImageFactorySpec) 38 | self.cxxWrapper = cxxWrapper 39 | return cxxWrapper 40 | } 41 | } 42 | } 43 | 44 | /** 45 | * A Swift base-protocol representing the WebImageFactory HybridObject. 46 | * Implement this protocol to create Swift-based instances of WebImageFactory. 47 | * ```swift 48 | * class HybridWebImageFactory : HybridWebImageFactorySpec { 49 | * // ... 50 | * } 51 | * ``` 52 | */ 53 | public typealias HybridWebImageFactorySpec = HybridWebImageFactorySpec_protocol & HybridWebImageFactorySpec_base 54 | -------------------------------------------------------------------------------- /packages/react-native-nitro-image/nitrogen/generated/shared/c++/HybridImageUtilsSpec.hpp: -------------------------------------------------------------------------------- 1 | /// 2 | /// HybridImageUtilsSpec.hpp 3 | /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. 4 | /// https://github.com/mrousavy/nitro 5 | /// Copyright © 2025 Marc Rousavy @ Margelo 6 | /// 7 | 8 | #pragma once 9 | 10 | #if __has_include() 11 | #include 12 | #else 13 | #error NitroModules cannot be found! Are you sure you installed NitroModules properly? 14 | #endif 15 | 16 | 17 | 18 | #include 19 | #include 20 | 21 | namespace margelo::nitro::image { 22 | 23 | using namespace margelo::nitro; 24 | 25 | /** 26 | * An abstract base class for `ImageUtils` 27 | * Inherit this class to create instances of `HybridImageUtilsSpec` in C++. 28 | * You must explicitly call `HybridObject`'s constructor yourself, because it is virtual. 29 | * @example 30 | * ```cpp 31 | * class HybridImageUtils: public HybridImageUtilsSpec { 32 | * public: 33 | * HybridImageUtils(...): HybridObject(TAG) { ... } 34 | * // ... 35 | * }; 36 | * ``` 37 | */ 38 | class HybridImageUtilsSpec: public virtual HybridObject { 39 | public: 40 | // Constructor 41 | explicit HybridImageUtilsSpec(): HybridObject(TAG) { } 42 | 43 | // Destructor 44 | ~HybridImageUtilsSpec() override = default; 45 | 46 | public: 47 | // Properties 48 | virtual bool getSupportsHeicLoading() = 0; 49 | virtual bool getSupportsHeicWriting() = 0; 50 | 51 | public: 52 | // Methods 53 | virtual std::string thumbHashToBase64String(const std::shared_ptr& thumbhash) = 0; 54 | virtual std::shared_ptr thumbhashFromBase64String(const std::string& thumbhashBase64) = 0; 55 | 56 | protected: 57 | // Hybrid Setup 58 | void loadHybridMethods() override; 59 | 60 | protected: 61 | // Tag for logging 62 | static constexpr auto TAG = "ImageUtils"; 63 | }; 64 | 65 | } // namespace margelo::nitro::image 66 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable/rn_edit_text_material.xml: -------------------------------------------------------------------------------- 1 | 2 | 16 | 22 | 23 | 24 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /packages/react-native-nitro-image/nitrogen/generated/ios/swift/Func_void_std__shared_ptr_HybridImageSpec_.swift: -------------------------------------------------------------------------------- 1 | /// 2 | /// Func_void_std__shared_ptr_HybridImageSpec_.swift 3 | /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. 4 | /// https://github.com/mrousavy/nitro 5 | /// Copyright © 2025 Marc Rousavy @ Margelo 6 | /// 7 | 8 | import NitroModules 9 | 10 | 11 | /** 12 | * Wraps a Swift `(_ value: (any HybridImageSpec)) -> Void` as a class. 13 | * This class can be used from C++, e.g. to wrap the Swift closure as a `std::function`. 14 | */ 15 | public final class Func_void_std__shared_ptr_HybridImageSpec_ { 16 | public typealias bridge = margelo.nitro.image.bridge.swift 17 | 18 | private let closure: (_ value: (any HybridImageSpec)) -> Void 19 | 20 | public init(_ closure: @escaping (_ value: (any HybridImageSpec)) -> Void) { 21 | self.closure = closure 22 | } 23 | 24 | @inline(__always) 25 | public func call(value: bridge.std__shared_ptr_HybridImageSpec_) -> Void { 26 | self.closure({ () -> HybridImageSpec in 27 | let __unsafePointer = bridge.get_std__shared_ptr_HybridImageSpec_(value) 28 | let __instance = HybridImageSpec_cxx.fromUnsafe(__unsafePointer) 29 | return __instance.getHybridImageSpec() 30 | }()) 31 | } 32 | 33 | /** 34 | * Casts this instance to a retained unsafe raw pointer. 35 | * This acquires one additional strong reference on the object! 36 | */ 37 | @inline(__always) 38 | public func toUnsafe() -> UnsafeMutableRawPointer { 39 | return Unmanaged.passRetained(self).toOpaque() 40 | } 41 | 42 | /** 43 | * Casts an unsafe pointer to a `Func_void_std__shared_ptr_HybridImageSpec_`. 44 | * The pointer has to be a retained opaque `Unmanaged`. 45 | * This removes one strong reference from the object! 46 | */ 47 | @inline(__always) 48 | public static func fromUnsafe(_ pointer: UnsafeMutableRawPointer) -> Func_void_std__shared_ptr_HybridImageSpec_ { 49 | return Unmanaged.fromOpaque(pointer).takeRetainedValue() 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /packages/react-native-nitro-web-image/nitrogen/generated/android/kotlin/com/margelo/nitro/web/image/HybridWebImageFactorySpec.kt: -------------------------------------------------------------------------------- 1 | /// 2 | /// HybridWebImageFactorySpec.kt 3 | /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. 4 | /// https://github.com/mrousavy/nitro 5 | /// Copyright © 2025 Marc Rousavy @ Margelo 6 | /// 7 | 8 | package com.margelo.nitro.web.image 9 | 10 | import androidx.annotation.Keep 11 | import com.facebook.jni.HybridData 12 | import com.facebook.proguard.annotations.DoNotStrip 13 | import com.margelo.nitro.image.HybridImageLoaderSpec 14 | import com.margelo.nitro.image.HybridImageSpec 15 | import com.margelo.nitro.core.Promise 16 | import com.margelo.nitro.core.HybridObject 17 | 18 | /** 19 | * A Kotlin class representing the WebImageFactory HybridObject. 20 | * Implement this abstract class to create Kotlin-based instances of WebImageFactory. 21 | */ 22 | @DoNotStrip 23 | @Keep 24 | @Suppress( 25 | "KotlinJniMissingFunction", "unused", 26 | "RedundantSuppression", "RedundantUnitReturnType", "SimpleRedundantLet", 27 | "LocalVariableName", "PropertyName", "PrivatePropertyName", "FunctionName" 28 | ) 29 | abstract class HybridWebImageFactorySpec: HybridObject() { 30 | @DoNotStrip 31 | private var mHybridData: HybridData = initHybrid() 32 | 33 | init { 34 | super.updateNative(mHybridData) 35 | } 36 | 37 | override fun updateNative(hybridData: HybridData) { 38 | mHybridData = hybridData 39 | super.updateNative(hybridData) 40 | } 41 | 42 | // Properties 43 | 44 | 45 | // Methods 46 | @DoNotStrip 47 | @Keep 48 | abstract fun createWebImageLoader(url: String, options: AsyncImageLoadOptions?): com.margelo.nitro.image.HybridImageLoaderSpec 49 | 50 | @DoNotStrip 51 | @Keep 52 | abstract fun loadFromURLAsync(url: String, options: AsyncImageLoadOptions?): Promise 53 | 54 | @DoNotStrip 55 | @Keep 56 | abstract fun preload(url: String): Unit 57 | 58 | private external fun initHybrid(): HybridData 59 | 60 | companion object { 61 | protected const val TAG = "HybridWebImageFactorySpec" 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /packages/react-native-nitro-image/nitrogen/generated/ios/NitroImageAutolinking.mm: -------------------------------------------------------------------------------- 1 | /// 2 | /// NitroImageAutolinking.mm 3 | /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. 4 | /// https://github.com/mrousavy/nitro 5 | /// Copyright © 2025 Marc Rousavy @ Margelo 6 | /// 7 | 8 | #import 9 | #import 10 | #import "NitroImage-Swift-Cxx-Umbrella.hpp" 11 | #import 12 | 13 | #include "HybridImageFactorySpecSwift.hpp" 14 | #include "HybridImageLoaderFactorySpecSwift.hpp" 15 | #include "HybridImageUtilsSpecSwift.hpp" 16 | #include "HybridNitroImageViewSpecSwift.hpp" 17 | 18 | @interface NitroImageAutolinking : NSObject 19 | @end 20 | 21 | @implementation NitroImageAutolinking 22 | 23 | + (void) load { 24 | using namespace margelo::nitro; 25 | using namespace margelo::nitro::image; 26 | 27 | HybridObjectRegistry::registerHybridObjectConstructor( 28 | "ImageFactory", 29 | []() -> std::shared_ptr { 30 | std::shared_ptr hybridObject = NitroImage::NitroImageAutolinking::createImageFactory(); 31 | return hybridObject; 32 | } 33 | ); 34 | HybridObjectRegistry::registerHybridObjectConstructor( 35 | "ImageLoaderFactory", 36 | []() -> std::shared_ptr { 37 | std::shared_ptr hybridObject = NitroImage::NitroImageAutolinking::createImageLoaderFactory(); 38 | return hybridObject; 39 | } 40 | ); 41 | HybridObjectRegistry::registerHybridObjectConstructor( 42 | "ImageUtils", 43 | []() -> std::shared_ptr { 44 | std::shared_ptr hybridObject = NitroImage::NitroImageAutolinking::createImageUtils(); 45 | return hybridObject; 46 | } 47 | ); 48 | HybridObjectRegistry::registerHybridObjectConstructor( 49 | "NitroImageView", 50 | []() -> std::shared_ptr { 51 | std::shared_ptr hybridObject = NitroImage::NitroImageAutolinking::createNitroImageView(); 52 | return hybridObject; 53 | } 54 | ); 55 | } 56 | 57 | @end 58 | -------------------------------------------------------------------------------- /packages/react-native-nitro-image/nitrogen/generated/shared/c++/HybridImageFactorySpec.cpp: -------------------------------------------------------------------------------- 1 | /// 2 | /// HybridImageFactorySpec.cpp 3 | /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. 4 | /// https://github.com/mrousavy/nitro 5 | /// Copyright © 2025 Marc Rousavy @ Margelo 6 | /// 7 | 8 | #include "HybridImageFactorySpec.hpp" 9 | 10 | namespace margelo::nitro::image { 11 | 12 | void HybridImageFactorySpec::loadHybridMethods() { 13 | // load base methods/properties 14 | HybridObject::loadHybridMethods(); 15 | // load custom methods/properties 16 | registerHybrids(this, [](Prototype& prototype) { 17 | prototype.registerHybridMethod("createBlankImage", &HybridImageFactorySpec::createBlankImage); 18 | prototype.registerHybridMethod("createBlankImageAsync", &HybridImageFactorySpec::createBlankImageAsync); 19 | prototype.registerHybridMethod("loadFromFile", &HybridImageFactorySpec::loadFromFile); 20 | prototype.registerHybridMethod("loadFromFileAsync", &HybridImageFactorySpec::loadFromFileAsync); 21 | prototype.registerHybridMethod("loadFromResources", &HybridImageFactorySpec::loadFromResources); 22 | prototype.registerHybridMethod("loadFromResourcesAsync", &HybridImageFactorySpec::loadFromResourcesAsync); 23 | prototype.registerHybridMethod("loadFromSymbol", &HybridImageFactorySpec::loadFromSymbol); 24 | prototype.registerHybridMethod("loadFromRawPixelData", &HybridImageFactorySpec::loadFromRawPixelData); 25 | prototype.registerHybridMethod("loadFromRawPixelDataAsync", &HybridImageFactorySpec::loadFromRawPixelDataAsync); 26 | prototype.registerHybridMethod("loadFromEncodedImageData", &HybridImageFactorySpec::loadFromEncodedImageData); 27 | prototype.registerHybridMethod("loadFromEncodedImageDataAsync", &HybridImageFactorySpec::loadFromEncodedImageDataAsync); 28 | prototype.registerHybridMethod("loadFromThumbHash", &HybridImageFactorySpec::loadFromThumbHash); 29 | prototype.registerHybridMethod("loadFromThumbHashAsync", &HybridImageFactorySpec::loadFromThumbHashAsync); 30 | }); 31 | } 32 | 33 | } // namespace margelo::nitro::image 34 | -------------------------------------------------------------------------------- /packages/react-native-nitro-web-image/android/src/main/java/com/margelo/nitro/web/image/ImageRequestBuilder+applyOptions.kt: -------------------------------------------------------------------------------- 1 | package com.margelo.nitro.web.image 2 | 3 | import coil3.annotation.ExperimentalCoilApi 4 | import coil3.decode.BlackholeDecoder 5 | import coil3.request.CachePolicy 6 | import coil3.request.ImageRequest 7 | import coil3.size.Precision 8 | 9 | @OptIn(ExperimentalCoilApi::class) 10 | fun ImageRequest.Builder.applyOptions(options: AsyncImageLoadOptions?): ImageRequest.Builder { 11 | if (options == null) return this 12 | var result = this 13 | 14 | if (options.priority != null) { 15 | options.priority.toCoroutineContext()?.let { context -> 16 | result = result.coroutineContext(context) 17 | } 18 | } 19 | 20 | if (options.forceRefresh == true) { 21 | // don't allow reading from cache, only writing. 22 | result = result.diskCachePolicy(CachePolicy.WRITE_ONLY) 23 | result = result.memoryCachePolicy(CachePolicy.WRITE_ONLY) 24 | result = result.networkCachePolicy(CachePolicy.WRITE_ONLY) 25 | } 26 | 27 | if (options.continueInBackground == true) { 28 | // TODO: Implement .continueInBackground 29 | } 30 | 31 | if (options.allowInvalidSSLCertificates == true) { 32 | // TODO: Implement .allowInvalidSSLCertificates 33 | } 34 | 35 | if (options.scaleDownLargeImages == true) { 36 | // Limit to 4096x4096 (~60 MB) 37 | result = result.size(4096, 4096) 38 | result = result.precision(Precision.INEXACT) 39 | } 40 | 41 | if (options.queryMemoryDataSync == true) { 42 | // TODO: Implement .queryMemoryDataSync 43 | } 44 | 45 | if (options.queryDiskDataSync == true) { 46 | // TODO: Implement .queryDiskDataSync 47 | } 48 | 49 | if (options.decodeImage == false) { 50 | result = result.decoderFactory(BlackholeDecoder.Factory()) 51 | } 52 | 53 | if (options.cacheKey != null) { 54 | result = result.diskCacheKey(options.cacheKey) 55 | result = result.memoryCacheKey(options.cacheKey) 56 | } 57 | 58 | return result 59 | } 60 | -------------------------------------------------------------------------------- /packages/react-native-nitro-image/nitrogen/generated/android/c++/views/JHybridNitroImageViewStateUpdater.hpp: -------------------------------------------------------------------------------- 1 | /// 2 | /// JHybridNitroImageViewStateUpdater.hpp 3 | /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. 4 | /// https://github.com/mrousavy/nitro 5 | /// Copyright © 2025 Marc Rousavy @ Margelo 6 | /// 7 | 8 | #pragma once 9 | 10 | #ifndef RN_SERIALIZABLE_STATE 11 | #error NitroImage was compiled without the 'RN_SERIALIZABLE_STATE' flag. This flag is required for Nitro Views - set it in your CMakeLists! 12 | #endif 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include "JHybridNitroImageViewSpec.hpp" 21 | #include "views/HybridNitroImageViewComponent.hpp" 22 | 23 | namespace margelo::nitro::image::views { 24 | 25 | using namespace facebook; 26 | 27 | class JHybridNitroImageViewStateUpdater: public jni::JavaClass { 28 | public: 29 | static constexpr auto kJavaDescriptor = "Lcom/margelo/nitro/image/views/HybridNitroImageViewStateUpdater;"; 30 | 31 | public: 32 | static void updateViewProps(jni::alias_ref /* class */, 33 | jni::alias_ref view, 34 | jni::alias_ref stateWrapperInterface); 35 | 36 | public: 37 | static void registerNatives() { 38 | // Register JNI calls 39 | javaClassStatic()->registerNatives({ 40 | makeNativeMethod("updateViewProps", JHybridNitroImageViewStateUpdater::updateViewProps), 41 | }); 42 | // Register React Native view component descriptor 43 | auto provider = react::concreteComponentDescriptorProvider(); 44 | auto providerRegistry = react::CoreComponentsRegistry::sharedProviderRegistry(); 45 | providerRegistry->add(provider); 46 | } 47 | }; 48 | 49 | } // namespace margelo::nitro::image::views 50 | -------------------------------------------------------------------------------- /example/android/gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | # Default value: -Xmx512m -XX:MaxMetaspaceSize=256m 13 | org.gradle.jvmargs=-Xmx2048m -XX:MaxMetaspaceSize=512m 14 | 15 | # When configured, Gradle will run in incubating parallel mode. 16 | # This option should only be used with decoupled projects. More details, visit 17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 18 | # org.gradle.parallel=true 19 | 20 | # AndroidX package structure to make it clearer which packages are bundled with the 21 | # Android operating system, and which are packaged with your app's APK 22 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 23 | android.useAndroidX=true 24 | 25 | # Use this property to specify which architecture you want to build. 26 | # You can also override it from the CLI using 27 | # ./gradlew -PreactNativeArchitectures=x86_64 28 | reactNativeArchitectures=armeabi-v7a,arm64-v8a,x86,x86_64 29 | 30 | # Use this property to enable support to the new architecture. 31 | # This will allow you to use TurboModules and the Fabric render in 32 | # your application. You should enable this flag either if you want 33 | # to write custom TurboModules/Fabric components OR use libraries that 34 | # are providing them. 35 | newArchEnabled=true 36 | 37 | # Use this property to enable or disable the Hermes JS engine. 38 | # If set to false, you will be using JSC instead. 39 | hermesEnabled=true 40 | 41 | # Use this property to enable edge-to-edge display support. 42 | # This allows your app to draw behind system bars for an immersive UI. 43 | # Note: Only works with ReactActivity and should not be used with custom Activity. 44 | edgeToEdgeEnabled=false 45 | -------------------------------------------------------------------------------- /packages/react-native-nitro-image/nitrogen/generated/android/kotlin/com/margelo/nitro/image/Variant_HybridImageSpec_HybridImageLoaderSpec.kt: -------------------------------------------------------------------------------- 1 | /// 2 | /// Variant_HybridImageSpec_HybridImageLoaderSpec.kt 3 | /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. 4 | /// https://github.com/mrousavy/nitro 5 | /// Copyright © 2025 Marc Rousavy @ Margelo 6 | /// 7 | 8 | package com.margelo.nitro.image 9 | 10 | import com.facebook.proguard.annotations.DoNotStrip 11 | 12 | 13 | /** 14 | * Represents the TypeScript variant "HybridImageSpec | HybridImageLoaderSpec". 15 | */ 16 | @Suppress("ClassName") 17 | @DoNotStrip 18 | sealed class Variant_HybridImageSpec_HybridImageLoaderSpec { 19 | @DoNotStrip 20 | data class First(@DoNotStrip val value: HybridImageSpec): Variant_HybridImageSpec_HybridImageLoaderSpec() 21 | @DoNotStrip 22 | data class Second(@DoNotStrip val value: HybridImageLoaderSpec): Variant_HybridImageSpec_HybridImageLoaderSpec() 23 | 24 | @Deprecated("getAs() is not type-safe. Use fold/asFirstOrNull/asSecondOrNull instead.", level = DeprecationLevel.ERROR) 25 | inline fun getAs(): T? = when (this) { 26 | is First -> value as? T 27 | is Second -> value as? T 28 | } 29 | 30 | val isFirst: Boolean 31 | get() = this is First 32 | val isSecond: Boolean 33 | get() = this is Second 34 | 35 | fun asFirstOrNull(): HybridImageSpec? { 36 | val value = (this as? First)?.value ?: return null 37 | return value 38 | } 39 | fun asSecondOrNull(): HybridImageLoaderSpec? { 40 | val value = (this as? Second)?.value ?: return null 41 | return value 42 | } 43 | 44 | inline fun match(first: (HybridImageSpec) -> R, second: (HybridImageLoaderSpec) -> R): R { 45 | return when (this) { 46 | is First -> first(value) 47 | is Second -> second(value) 48 | } 49 | } 50 | 51 | companion object { 52 | @JvmStatic 53 | @DoNotStrip 54 | fun create(value: HybridImageSpec): Variant_HybridImageSpec_HybridImageLoaderSpec = First(value) 55 | @JvmStatic 56 | @DoNotStrip 57 | fun create(value: HybridImageLoaderSpec): Variant_HybridImageSpec_HybridImageLoaderSpec = Second(value) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /packages/react-native-nitro-image/nitrogen/generated/android/kotlin/com/margelo/nitro/image/HybridImageLoaderFactorySpec.kt: -------------------------------------------------------------------------------- 1 | /// 2 | /// HybridImageLoaderFactorySpec.kt 3 | /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. 4 | /// https://github.com/mrousavy/nitro 5 | /// Copyright © 2025 Marc Rousavy @ Margelo 6 | /// 7 | 8 | package com.margelo.nitro.image 9 | 10 | import androidx.annotation.Keep 11 | import com.facebook.jni.HybridData 12 | import com.facebook.proguard.annotations.DoNotStrip 13 | import com.margelo.nitro.core.HybridObject 14 | 15 | /** 16 | * A Kotlin class representing the ImageLoaderFactory HybridObject. 17 | * Implement this abstract class to create Kotlin-based instances of ImageLoaderFactory. 18 | */ 19 | @DoNotStrip 20 | @Keep 21 | @Suppress( 22 | "KotlinJniMissingFunction", "unused", 23 | "RedundantSuppression", "RedundantUnitReturnType", "SimpleRedundantLet", 24 | "LocalVariableName", "PropertyName", "PrivatePropertyName", "FunctionName" 25 | ) 26 | abstract class HybridImageLoaderFactorySpec: HybridObject() { 27 | @DoNotStrip 28 | private var mHybridData: HybridData = initHybrid() 29 | 30 | init { 31 | super.updateNative(mHybridData) 32 | } 33 | 34 | override fun updateNative(hybridData: HybridData) { 35 | mHybridData = hybridData 36 | super.updateNative(hybridData) 37 | } 38 | 39 | // Properties 40 | 41 | 42 | // Methods 43 | @DoNotStrip 44 | @Keep 45 | abstract fun createFileImageLoader(filePath: String): HybridImageLoaderSpec 46 | 47 | @DoNotStrip 48 | @Keep 49 | abstract fun createResourceImageLoader(name: String): HybridImageLoaderSpec 50 | 51 | @DoNotStrip 52 | @Keep 53 | abstract fun createSymbolImageLoader(symbolName: String): HybridImageLoaderSpec 54 | 55 | @DoNotStrip 56 | @Keep 57 | abstract fun createRawPixelDataImageLoader(data: RawPixelData): HybridImageLoaderSpec 58 | 59 | @DoNotStrip 60 | @Keep 61 | abstract fun createEncodedImageDataImageLoader(data: EncodedImageData): HybridImageLoaderSpec 62 | 63 | private external fun initHybrid(): HybridData 64 | 65 | companion object { 66 | protected const val TAG = "HybridImageLoaderFactorySpec" 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /packages/react-native-nitro-image/nitrogen/generated/ios/swift/HybridImageLoaderFactorySpec.swift: -------------------------------------------------------------------------------- 1 | /// 2 | /// HybridImageLoaderFactorySpec.swift 3 | /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. 4 | /// https://github.com/mrousavy/nitro 5 | /// Copyright © 2025 Marc Rousavy @ Margelo 6 | /// 7 | 8 | import Foundation 9 | import NitroModules 10 | 11 | /// See ``HybridImageLoaderFactorySpec`` 12 | public protocol HybridImageLoaderFactorySpec_protocol: HybridObject { 13 | // Properties 14 | 15 | 16 | // Methods 17 | func createFileImageLoader(filePath: String) throws -> (any HybridImageLoaderSpec) 18 | func createResourceImageLoader(name: String) throws -> (any HybridImageLoaderSpec) 19 | func createSymbolImageLoader(symbolName: String) throws -> (any HybridImageLoaderSpec) 20 | func createRawPixelDataImageLoader(data: RawPixelData) throws -> (any HybridImageLoaderSpec) 21 | func createEncodedImageDataImageLoader(data: EncodedImageData) throws -> (any HybridImageLoaderSpec) 22 | } 23 | 24 | /// See ``HybridImageLoaderFactorySpec`` 25 | open class HybridImageLoaderFactorySpec_base { 26 | private weak var cxxWrapper: HybridImageLoaderFactorySpec_cxx? = nil 27 | public init() { } 28 | public func getCxxWrapper() -> HybridImageLoaderFactorySpec_cxx { 29 | #if DEBUG 30 | guard self is HybridImageLoaderFactorySpec else { 31 | fatalError("`self` is not a `HybridImageLoaderFactorySpec`! Did you accidentally inherit from `HybridImageLoaderFactorySpec_base` instead of `HybridImageLoaderFactorySpec`?") 32 | } 33 | #endif 34 | if let cxxWrapper = self.cxxWrapper { 35 | return cxxWrapper 36 | } else { 37 | let cxxWrapper = HybridImageLoaderFactorySpec_cxx(self as! HybridImageLoaderFactorySpec) 38 | self.cxxWrapper = cxxWrapper 39 | return cxxWrapper 40 | } 41 | } 42 | } 43 | 44 | /** 45 | * A Swift base-protocol representing the ImageLoaderFactory HybridObject. 46 | * Implement this protocol to create Swift-based instances of ImageLoaderFactory. 47 | * ```swift 48 | * class HybridImageLoaderFactory : HybridImageLoaderFactorySpec { 49 | * // ... 50 | * } 51 | * ``` 52 | */ 53 | public typealias HybridImageLoaderFactorySpec = HybridImageLoaderFactorySpec_protocol & HybridImageLoaderFactorySpec_base 54 | -------------------------------------------------------------------------------- /packages/react-native-nitro-image/nitrogen/generated/shared/c++/HybridImageSpec.cpp: -------------------------------------------------------------------------------- 1 | /// 2 | /// HybridImageSpec.cpp 3 | /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. 4 | /// https://github.com/mrousavy/nitro 5 | /// Copyright © 2025 Marc Rousavy @ Margelo 6 | /// 7 | 8 | #include "HybridImageSpec.hpp" 9 | 10 | namespace margelo::nitro::image { 11 | 12 | void HybridImageSpec::loadHybridMethods() { 13 | // load base methods/properties 14 | HybridObject::loadHybridMethods(); 15 | // load custom methods/properties 16 | registerHybrids(this, [](Prototype& prototype) { 17 | prototype.registerHybridGetter("width", &HybridImageSpec::getWidth); 18 | prototype.registerHybridGetter("height", &HybridImageSpec::getHeight); 19 | prototype.registerHybridMethod("toRawPixelData", &HybridImageSpec::toRawPixelData); 20 | prototype.registerHybridMethod("toRawPixelDataAsync", &HybridImageSpec::toRawPixelDataAsync); 21 | prototype.registerHybridMethod("toEncodedImageData", &HybridImageSpec::toEncodedImageData); 22 | prototype.registerHybridMethod("toEncodedImageDataAsync", &HybridImageSpec::toEncodedImageDataAsync); 23 | prototype.registerHybridMethod("resize", &HybridImageSpec::resize); 24 | prototype.registerHybridMethod("resizeAsync", &HybridImageSpec::resizeAsync); 25 | prototype.registerHybridMethod("rotate", &HybridImageSpec::rotate); 26 | prototype.registerHybridMethod("rotateAsync", &HybridImageSpec::rotateAsync); 27 | prototype.registerHybridMethod("crop", &HybridImageSpec::crop); 28 | prototype.registerHybridMethod("cropAsync", &HybridImageSpec::cropAsync); 29 | prototype.registerHybridMethod("saveToFileAsync", &HybridImageSpec::saveToFileAsync); 30 | prototype.registerHybridMethod("saveToTemporaryFileAsync", &HybridImageSpec::saveToTemporaryFileAsync); 31 | prototype.registerHybridMethod("toThumbHash", &HybridImageSpec::toThumbHash); 32 | prototype.registerHybridMethod("toThumbHashAsync", &HybridImageSpec::toThumbHashAsync); 33 | prototype.registerHybridMethod("renderInto", &HybridImageSpec::renderInto); 34 | prototype.registerHybridMethod("renderIntoAsync", &HybridImageSpec::renderIntoAsync); 35 | }); 36 | } 37 | 38 | } // namespace margelo::nitro::image 39 | -------------------------------------------------------------------------------- /packages/react-native-nitro-image/ios/HybridImageLoader.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HybridImageLoader.swift 3 | // Pods 4 | // 5 | // Created by Marc Rousavy on 25.07.25. 6 | // 7 | 8 | import NitroModules 9 | 10 | class HybridImageLoader: HybridImageLoaderSpec { 11 | typealias LoadFunc = () throws -> Promise 12 | typealias RequestFunc = (_ for: any HybridNitroImageViewSpec) throws -> Void 13 | typealias DropFunc = (_ for: any HybridNitroImageViewSpec) throws -> Void 14 | 15 | private let load: LoadFunc 16 | private let requestImage: RequestFunc 17 | private let dropImage: DropFunc 18 | 19 | init(load: @escaping LoadFunc) { 20 | self.load = load 21 | self.requestImage = Self.defaultRequestFunc(forLoadFunc: load) 22 | self.dropImage = Self.defaultDropFunc() 23 | } 24 | init(load: @escaping LoadFunc, 25 | requestImage: @escaping RequestFunc, 26 | dropImage: @escaping DropFunc) { 27 | self.load = load 28 | self.requestImage = requestImage 29 | self.dropImage = dropImage 30 | } 31 | 32 | func loadImage() throws -> Promise { 33 | return try load() 34 | } 35 | 36 | func requestImage(forView view: any HybridNitroImageViewSpec) throws { 37 | return try requestImage(view) 38 | } 39 | 40 | func dropImage(forView view: any HybridNitroImageViewSpec) throws { 41 | return try dropImage(view) 42 | } 43 | } 44 | 45 | fileprivate extension HybridImageLoader { 46 | static func defaultRequestFunc(forLoadFunc loadFunc: @escaping LoadFunc) -> RequestFunc { 47 | return { view in 48 | guard let view = view as? NativeImageView else { return } 49 | let promise = try loadFunc() 50 | promise.then { image in 51 | guard let image = image as? NativeImage else { return } 52 | Task { @MainActor in 53 | view.imageView.image = image.uiImage 54 | } 55 | } 56 | promise.catch { _ in 57 | Task { @MainActor in 58 | view.imageView.image = nil 59 | } 60 | } 61 | } 62 | } 63 | static func defaultDropFunc() -> DropFunc { 64 | return { view in 65 | guard let view = view as? NativeImageView else { return } 66 | Task { @MainActor in 67 | view.imageView.image = nil 68 | } 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /packages/react-native-nitro-image/nitrogen/generated/android/c++/JImageFormat.hpp: -------------------------------------------------------------------------------- 1 | /// 2 | /// JImageFormat.hpp 3 | /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. 4 | /// https://github.com/mrousavy/nitro 5 | /// Copyright © 2025 Marc Rousavy @ Margelo 6 | /// 7 | 8 | #pragma once 9 | 10 | #include 11 | #include "ImageFormat.hpp" 12 | 13 | namespace margelo::nitro::image { 14 | 15 | using namespace facebook; 16 | 17 | /** 18 | * The C++ JNI bridge between the C++ enum "ImageFormat" and the the Kotlin enum "ImageFormat". 19 | */ 20 | struct JImageFormat final: public jni::JavaClass { 21 | public: 22 | static auto constexpr kJavaDescriptor = "Lcom/margelo/nitro/image/ImageFormat;"; 23 | 24 | public: 25 | /** 26 | * Convert this Java/Kotlin-based enum to the C++ enum ImageFormat. 27 | */ 28 | [[maybe_unused]] 29 | [[nodiscard]] 30 | ImageFormat toCpp() const { 31 | static const auto clazz = javaClassStatic(); 32 | static const auto fieldOrdinal = clazz->getField("value"); 33 | int ordinal = this->getFieldValue(fieldOrdinal); 34 | return static_cast(ordinal); 35 | } 36 | 37 | public: 38 | /** 39 | * Create a Java/Kotlin-based enum with the given C++ enum's value. 40 | */ 41 | [[maybe_unused]] 42 | static jni::alias_ref fromCpp(ImageFormat value) { 43 | static const auto clazz = javaClassStatic(); 44 | static const auto fieldJPG = clazz->getStaticField("JPG"); 45 | static const auto fieldPNG = clazz->getStaticField("PNG"); 46 | static const auto fieldHEIC = clazz->getStaticField("HEIC"); 47 | 48 | switch (value) { 49 | case ImageFormat::JPG: 50 | return clazz->getStaticFieldValue(fieldJPG); 51 | case ImageFormat::PNG: 52 | return clazz->getStaticFieldValue(fieldPNG); 53 | case ImageFormat::HEIC: 54 | return clazz->getStaticFieldValue(fieldHEIC); 55 | default: 56 | std::string stringValue = std::to_string(static_cast(value)); 57 | throw std::invalid_argument("Invalid enum value (" + stringValue + "!"); 58 | } 59 | } 60 | }; 61 | 62 | } // namespace margelo::nitro::image 63 | -------------------------------------------------------------------------------- /packages/react-native-nitro-image/nitrogen/generated/ios/NitroImage+autolinking.rb: -------------------------------------------------------------------------------- 1 | # 2 | # NitroImage+autolinking.rb 3 | # This file was generated by nitrogen. DO NOT MODIFY THIS FILE. 4 | # https://github.com/mrousavy/nitro 5 | # Copyright © 2025 Marc Rousavy @ Margelo 6 | # 7 | 8 | # This is a Ruby script that adds all files generated by Nitrogen 9 | # to the given podspec. 10 | # 11 | # To use it, add this to your .podspec: 12 | # ```ruby 13 | # Pod::Spec.new do |spec| 14 | # # ... 15 | # 16 | # # Add all files generated by Nitrogen 17 | # load 'nitrogen/generated/ios/NitroImage+autolinking.rb' 18 | # add_nitrogen_files(spec) 19 | # end 20 | # ``` 21 | 22 | def add_nitrogen_files(spec) 23 | Pod::UI.puts "[NitroModules] 🔥 NitroImage is boosted by nitro!" 24 | 25 | spec.dependency "NitroModules" 26 | 27 | current_source_files = Array(spec.attributes_hash['source_files']) 28 | spec.source_files = current_source_files + [ 29 | # Generated cross-platform specs 30 | "nitrogen/generated/shared/**/*.{h,hpp,c,cpp,swift}", 31 | # Generated bridges for the cross-platform specs 32 | "nitrogen/generated/ios/**/*.{h,hpp,c,cpp,mm,swift}", 33 | ] 34 | 35 | current_public_header_files = Array(spec.attributes_hash['public_header_files']) 36 | spec.public_header_files = current_public_header_files + [ 37 | # Generated specs 38 | "nitrogen/generated/shared/**/*.{h,hpp}", 39 | # Swift to C++ bridging helpers 40 | "nitrogen/generated/ios/NitroImage-Swift-Cxx-Bridge.hpp" 41 | ] 42 | 43 | current_private_header_files = Array(spec.attributes_hash['private_header_files']) 44 | spec.private_header_files = current_private_header_files + [ 45 | # iOS specific specs 46 | "nitrogen/generated/ios/c++/**/*.{h,hpp}", 47 | # Views are framework-specific and should be private 48 | "nitrogen/generated/shared/**/views/**/*" 49 | ] 50 | 51 | current_pod_target_xcconfig = spec.attributes_hash['pod_target_xcconfig'] || {} 52 | spec.pod_target_xcconfig = current_pod_target_xcconfig.merge({ 53 | # Use C++ 20 54 | "CLANG_CXX_LANGUAGE_STANDARD" => "c++20", 55 | # Enables C++ <-> Swift interop (by default it's only C) 56 | "SWIFT_OBJC_INTEROP_MODE" => "objcxx", 57 | # Enables stricter modular headers 58 | "DEFINES_MODULE" => "YES", 59 | }) 60 | end 61 | --------------------------------------------------------------------------------