From df87952ad17b8818411c51ccd23624d18994809c Mon Sep 17 00:00:00 2001 From: bglgwyng Date: Sat, 5 Jul 2025 20:37:54 +0900 Subject: [PATCH 01/24] feat: add `loadFromAssetAsync` --- bun.lock | 5 +- example/ios/NitroImageExample/Info.plist | 2 + example/ios/Podfile.lock | 140 +++++++++++------- example/package.json | 1 + example/src/App.tsx | 31 ++-- example/src/NitroMediaLibraryImageTab.tsx | 65 ++++++++ ios/HybridImageFactory.swift | 55 +++++++ .../android/c++/JHybridImageFactorySpec.cpp | 16 ++ .../android/c++/JHybridImageFactorySpec.hpp | 1 + .../nitro/image/HybridImageFactorySpec.kt | 4 + .../ios/c++/HybridImageFactorySpecSwift.hpp | 8 + .../ios/swift/HybridImageFactorySpec.swift | 1 + .../swift/HybridImageFactorySpec_cxx.swift | 22 +++ .../shared/c++/HybridImageFactorySpec.cpp | 1 + .../shared/c++/HybridImageFactorySpec.hpp | 1 + src/ImageFactory.ts | 8 + src/specs/ImageFactory.nitro.ts | 7 + 17 files changed, 295 insertions(+), 73 deletions(-) create mode 100644 example/src/NitroMediaLibraryImageTab.tsx diff --git a/bun.lock b/bun.lock index e1d16022..ad58d321 100644 --- a/bun.lock +++ b/bun.lock @@ -28,6 +28,7 @@ "name": "NitroImageExample", "version": "0.0.1", "dependencies": { + "@react-native-camera-roll/camera-roll": "^7.10.1", "@react-navigation/bottom-tabs": "^7.3.14", "@react-navigation/native": "^7.1.10", "react": "19.0.0", @@ -389,6 +390,8 @@ "@pnpm/npm-conf": ["@pnpm/npm-conf@2.3.1", "", { "dependencies": { "@pnpm/config.env-replace": "^1.1.0", "@pnpm/network.ca-file": "^1.0.1", "config-chain": "^1.1.11" } }, "sha512-c83qWb22rNRuB0UaVCI0uRPNRr8Z0FWnEIvT47jiHAmOIUHbBOg5XvV7pM5x+rKn9HRpjxquDbXYSXr3fAKFcw=="], + "@react-native-camera-roll/camera-roll": ["@react-native-camera-roll/camera-roll@7.10.1", "", { "peerDependencies": { "react-native": ">=0.59" } }, "sha512-6zuK+E+z3a4Nij5OrkMh9BL7J1/Eg0PB8iX7/chNwhghpTZ93cr3Zrj/02ueglN0BV/tIKmb+BDERfzVIGRT7w=="], + "@react-native-community/cli": ["@react-native-community/cli@18.0.0", "", { "dependencies": { "@react-native-community/cli-clean": "18.0.0", "@react-native-community/cli-config": "18.0.0", "@react-native-community/cli-doctor": "18.0.0", "@react-native-community/cli-server-api": "18.0.0", "@react-native-community/cli-tools": "18.0.0", "@react-native-community/cli-types": "18.0.0", "chalk": "^4.1.2", "commander": "^9.4.1", "deepmerge": "^4.3.0", "execa": "^5.0.0", "find-up": "^5.0.0", "fs-extra": "^8.1.0", "graceful-fs": "^4.1.3", "prompts": "^2.4.2", "semver": "^7.5.2" }, "bin": { "rnc-cli": "build/bin.js" } }, "sha512-DyKptlG78XPFo7tDod+we5a3R+U9qjyhaVFbOPvH4pFNu5Dehewtol/srl44K6Cszq0aEMlAJZ3juk0W4WnOJA=="], "@react-native-community/cli-clean": ["@react-native-community/cli-clean@18.0.0", "", { "dependencies": { "@react-native-community/cli-tools": "18.0.0", "chalk": "^4.1.2", "execa": "^5.0.0", "fast-glob": "^3.3.2" } }, "sha512-+k64EnJaMI5U8iNDF9AftHBJW+pO/isAhncEXuKRc6IjRtIh6yoaUIIf5+C98fgjfux7CNRZAMQIkPbZodv2Gw=="], @@ -1891,7 +1894,7 @@ "NitroImageExample/prettier": ["prettier@2.8.8", "", { "bin": { "prettier": "bin-prettier.js" } }, "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q=="], - "NitroImageExample/react-native-nitro-image": ["react-native-nitro-image@file:", { "devDependencies": { "@react-native/eslint-config": "0.79.3", "@release-it/conventional-changelog": "^8.0.2", "@types/react": "^19.0.6", "eslint": "^8.57.0", "eslint-config-prettier": "^9.1.0", "eslint-plugin-prettier": "^5.2.1", "nitro-codegen": "*", "prettier": "^3.3.3", "react": "19.0.0", "react-native": "0.79.3", "react-native-nitro-modules": "*", "release-it": "^17.10.0", "typescript": "^5.5.4" }, "peerDependencies": { "react": "*", "react-native": "*", "react-native-nitro-modules": "*" } }], + "NitroImageExample/react-native-nitro-image": ["react-native-nitro-image@file:", { "devDependencies": { "@react-native/eslint-config": "0.79.3", "@release-it/conventional-changelog": "^8.0.2", "@types/react": "^19.0.6", "eslint": "^8.57.0", "eslint-config-prettier": "^9.1.0", "eslint-plugin-prettier": "^5.2.1", "nitro-codegen": "*", "prettier": "^3.3.3", "react": "19.0.0", "react-native": "0.79.3", "react-native-nitro-modules": "^0.26.3", "release-it": "^17.10.0", "typescript": "^5.5.4" }, "peerDependencies": { "react": "*", "react-native": "*", "react-native-nitro-modules": "*" } }], "NitroImageExample/typescript": ["typescript@5.0.4", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw=="], diff --git a/example/ios/NitroImageExample/Info.plist b/example/ios/NitroImageExample/Info.plist index bdb6c8c0..b09ea99a 100644 --- a/example/ios/NitroImageExample/Info.plist +++ b/example/ios/NitroImageExample/Info.plist @@ -33,6 +33,8 @@ NSLocationWhenInUseUsageDescription + NSPhotoLibraryUsageDescription + UILaunchStoryboardName LaunchScreen UIRequiredDeviceCapabilities diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index 34721262..b039e290 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -1396,6 +1396,30 @@ PODS: - React-jsiexecutor - React-RCTFBReactNativeSpec - ReactCommon/turbomodule/core + - react-native-cameraroll (7.10.1): + - DoubleConversion + - glog + - hermes-engine + - RCT-Folly (= 2024.11.18.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-hermes + - React-ImageManager + - React-jsi + - React-NativeModulesApple + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - Yoga - react-native-safe-area-context (5.4.1): - DoubleConversion - glog @@ -1900,6 +1924,7 @@ DEPENDENCIES: - React-logger (from `../../node_modules/react-native/ReactCommon/logger`) - React-Mapbuffer (from `../../node_modules/react-native/ReactCommon`) - React-microtasksnativemodule (from `../../node_modules/react-native/ReactCommon/react/nativemodule/microtasks`) + - "react-native-cameraroll (from `../../node_modules/@react-native-camera-roll/camera-roll`)" - react-native-safe-area-context (from `../../node_modules/react-native-safe-area-context`) - React-NativeModulesApple (from `../../node_modules/react-native/ReactCommon/react/nativemodule/core/platform/ios`) - React-oscompat (from `../../node_modules/react-native/ReactCommon/oscompat`) @@ -2026,6 +2051,8 @@ EXTERNAL SOURCES: :path: "../../node_modules/react-native/ReactCommon" React-microtasksnativemodule: :path: "../../node_modules/react-native/ReactCommon/react/nativemodule/microtasks" + react-native-cameraroll: + :path: "../../node_modules/@react-native-camera-roll/camera-roll" react-native-safe-area-context: :path: "../../node_modules/react-native-safe-area-context" React-NativeModulesApple: @@ -2106,78 +2133,79 @@ SPEC CHECKSUMS: glog: 5683914934d5b6e4240e497e0f4a3b42d1854183 hermes-engine: 94ed01537bdeccaab1adbf94b040d115d6fa1a7f libwebp: 02b23773aedb6ff1fd38cec7a77b81414c6842a8 - NitroImage: 2eafe632dcaca97182171c586f1f649a56d69270 - NitroModules: 93590f0d4be83cfa9fef0f9f3feb4f95af6da2fc - RCT-Folly: 36fe2295e44b10d831836cc0d1daec5f8abcf809 + NitroImage: 7f0a8fda3268c7169c9f4f23437d5841cbf4c96a + NitroModules: f36b94e48ff1705fc6b84bc1953f40e2da4196c2 + RCT-Folly: e78785aa9ba2ed998ea4151e314036f6c49e6d82 RCTDeprecation: c3e3f5b4ea83e7ff3bc86ce09e2a54b7affd687d RCTRequired: ee438439880dffc9425930d1dd1a3c883ee6879c RCTTypeSafety: fe728195791e1a0222aa83596a570cf377cd475e React: 114ee161feb204412580928b743e6716aebac987 React-callinvoker: d175cf3640a993f6cd960044a7657543157f0ba9 - React-Core: e84d47ce3df8dde567f5b9668f6103f8e562d72a - React-CoreModules: ce8e588dca54cd790e2d424d0e678924e62b41b1 - React-cxxreact: 2c10954abacc35e876adf46e25ebfd74a0106521 + React-Core: cd487c9eeb125c902242bcc76ced12e14adf4ea4 + React-CoreModules: 202df4f342e5c2893d5d1899b2856879a90d4bf1 + React-cxxreact: 72be57cebb9976199e6130ec6f9d511c6ae3b310 React-debug: 5414189118050ebad6c701942c01c63bb499f5a6 - React-defaultsnativemodule: fdde92d2e675382f275cd2e4d09adf553d67cad8 - React-domnativemodule: 8557aaaaf238dede0f717d1d8af5a0738301ab2b - React-Fabric: c7e8258e0a2b059f52fa17c43e73840f11dcebc0 - React-FabricComponents: 64f9152449dcd5b5e56109bed1b8fccd502c7ea3 - React-FabricImage: 0357eaaa48d9faae8250647ed843379c33b8e9e9 + React-defaultsnativemodule: d9683a9187744c14c6ce7eb1a1a3649d33591e43 + React-domnativemodule: ea54d8fd1ee946a97654635f5a4d9245f6588006 + React-Fabric: 3cdce860fd1079c27f8cd8e029f716e6c4b34a4e + React-FabricComponents: 126c59bb8d69d795492e2a2c97a0c1738a29730b + React-FabricImage: 6cf335909c59746e7aca2180901367978cc21959 React-featureflags: 670eb7cdf1495ea7bf392f653c19268291acaad1 - React-featureflagsnativemodule: 7c00e55641b40f8b756a8782daa19d905762381d - React-graphics: c90ff68c04d51c184afb6d60646bddb049a2fe10 - React-hermes: 2a9fb8df8a1be5e5b065c91d7b0fad072554055e - React-idlecallbacksnativemodule: 740dd584b66c31fe5f728b8dff4bf6b423074bce - React-ImageManager: 57474ab8176ce1778188326912370ed452712fe2 - React-jserrorhandler: e1c5c0eb4c307ee8bea7e4ce5c34f9952a771060 - React-jsi: 606a42a46f9a7299c1757686c6856eca8346754b - React-jsiexecutor: 762ee9c7962597d2f168afee00a0cac7573e6e48 - React-jsinspector: 1c392e230b5a7bb58081021f3be5d17858eb1ef5 - React-jsinspectortracing: 09132689843f824945b68895daf5fc7e27d71742 - React-jsitooling: 9f834140bdeebb07b1503790b1066acb862e1c53 - React-jsitracing: 9535edb956a9784fa406e35c05c83a45a64ca403 - React-logger: 44e070aefe084568c02b544b9d7d436703be1a47 - React-Mapbuffer: b25dedf7fe8923898c44b217ee2ef75c837d0e6a - React-microtasksnativemodule: 22bd119cb27aa28ff15a4e9311e1a53034c14eab - react-native-safe-area-context: dc279397abf103b0577f48c548630b7e639646a7 - React-NativeModulesApple: 28df2c2241d8e2be46db34179f86ad76a3663068 + React-featureflagsnativemodule: 16b4eae0bf4d838e0a807c6b0cde2b4ae84534ef + React-graphics: 0d6b3201d0414e56897f09693df82d601cac0613 + React-hermes: a40e47b18c31efe4baa7942357e2327ddab63918 + React-idlecallbacksnativemodule: 37c6d6053ad5123405b0fbb695c44841273482dd + React-ImageManager: 1f5cb695a06454329759bfce8548ac0d4fcd069e + React-jserrorhandler: a8214a9f297af6ee3cb004e2cb5185214cfc4360 + React-jsi: ae02c9d6d68dbed80a9fde8f6d6198035ca154ce + React-jsiexecutor: 8c266057f23430685a2d928703e77eda80e1742e + React-jsinspector: 8789c28cbd63ff818d23550556490883caa89cdb + React-jsinspectortracing: 150180f7ed6fd2252308b5608b62ea698ca087b6 + React-jsitooling: 1fd5c99a3688a5152781be4ecfb88ca9c6cb11d8 + React-jsitracing: c87b3d789f4d5053a2518fb8202c1e1ccd6848a9 + React-logger: 514fac028fee60c84591f951c7c04ba1c5023334 + React-Mapbuffer: fae8da2c01aeb7f26ad739731b6dba61fd02fd97 + React-microtasksnativemodule: 20454ffccff553f0ee73fd20873aa8555a5867fb + react-native-cameraroll: 41084e42ab4ec08940452737aca3fd5e0edc63fe + react-native-safe-area-context: 5594ec631ede9c311c5c0efa244228eff845ce88 + React-NativeModulesApple: 65b2735133d6ce8a3cb5f23215ef85e427b0139c React-oscompat: f26aa2a4adc84c34212ab12c07988fe19e9cf16a - React-perflogger: fb196ad3fa3263afc55f773a10c2741517a27f7c - React-performancetimeline: 4979f4bf1c13bd4b8050e5599c92c0c8a5f4f7ad + React-perflogger: e15a0d43d1928e1c82f4f0b7fc05f7e9bccfede8 + React-performancetimeline: 064f2767a5d4d71547ea32a3cd8a76a101dfd11f React-RCTActionSheet: c89c8b9b7c3ef87cb6a67e20f5eaea271f4b5f67 - React-RCTAnimation: 8cff4eda84c7e70c674c50763c724c660ae7e56c - React-RCTAppDelegate: 12b784fb29e25a606aaf869d11efb4ae97bb81b3 - React-RCTBlob: 105ead00cc3cb7ed4180481cec7cb68829c0c16b - React-RCTFabric: 1b51a08f06f61bd757b3b3bf8e38240cbd969056 - React-RCTFBReactNativeSpec: c3bfd143e072358d0d8b7efc97fb6a09e77f8f46 - React-RCTImage: ef3831114706dbb9ccab839abe804edef1e1faab - React-RCTLinking: eadceef820a11dd2bc7b4b569406eacc637c7f82 - React-RCTNetwork: 9e1323f0cdfaf0f561d8a6667363cc8deadf41e8 - React-RCTRuntime: 2427ecba97fbfdd430b443ee4e5cb1ca195897b2 - React-RCTSettings: 482bb7da0e94823cd1a36edd408c85abb2d2e42a - React-RCTText: d103fac423b92be0ed295767ea4c2ecc1f4389fd - React-RCTVibration: 816504f335105f0682467823400436e18ec98b7b + React-RCTAnimation: e00af558ccb5fedd380ae32329be4c38e92e9b90 + React-RCTAppDelegate: 10d98d4867643322fa4fcd04548359ac88c74656 + React-RCTBlob: ef645bccf9c33d3b4391794983744da897474dfb + React-RCTFabric: 06ff9416fc48742bba58ed81a0d0a62bf0f8c7ec + React-RCTFBReactNativeSpec: e0942c2c7efa10303c63e287c1c1788aeb6d99ef + React-RCTImage: 0e3669a0bda8995874736d0f8f12c21d522df3c4 + React-RCTLinking: bd81ec3d1b6686a7c58bc8ed8b7a1f05ff2b3f8b + React-RCTNetwork: 20b8044841a043b80e7027e1bc4049ffa552d1fa + React-RCTRuntime: 0084733b33619670bea35cb02c96412d9271718e + React-RCTSettings: fa1d3e6c302e9980b5670315e2ccc998255ce32a + React-RCTText: 71f01a9261c015b76702e9d7a4153c9ca45f2341 + React-RCTVibration: 0e05fa4647ec1391c409fcc1cbd7cdb4894d80ef React-rendererconsistency: 6a79c0a31890942940480f6e761365c4f604394f - React-renderercss: 7635fe6c07a8e2e2e428c022bdc8475986e714d2 - React-rendererdebug: cc2fb12e1a64342a970c1b45e88549fe8a41d7e4 + React-renderercss: 18c7ae4971ae6eb6c6c1d4c8f241a856a8e4243f + React-rendererdebug: d621c08946310b44e58a80b6bf96a6c13e779cff React-rncore: 91456f1e8feadf5216b37073968c16c14061f8d7 - React-RuntimeApple: 0bbd2594e65b794e3ca8c678008b7b9a746c03eb - React-RuntimeCore: 9feee9dbf2e7d6db7770655026c4a7577c5b21ab + React-RuntimeApple: 013c318ce9b3506b4fc7abe67369fdd14fc18bea + React-RuntimeCore: 66eaaf42eae393a1d592557493a70b80f051f885 React-runtimeexecutor: 4e7bc0119ff38f80df43d109ef9508497cac1eee - React-RuntimeHermes: 177ec426c0307f4eda0d9ece5a5d7424aaf2ce6c - React-runtimescheduler: 0d063fb7f7b4dcc1032503f2180e2537ad710e40 + React-RuntimeHermes: 2878d6e471ac3eb88ecc946d07938c4f9ec4f63e + React-runtimescheduler: ea0278e84e37a64a0f02b5bcb98fec1d49450fe7 React-timing: 4e298a80e9a41c31d884df0422c9eb73a240ec0d - React-utils: f2cfe205e3206aac6a263ce749f5ddb62d0b238d - ReactAppDependencyProvider: 552391af67c115d8ee20f9359711e7821145cd96 - ReactCodegen: a38bb58167580e1823678d788e0ee6060e2d8b4a - ReactCommon: b7062f81f6ea61ec29e03aab1d439fb56c49372c - RNFastImage: 5c9c9fed9c076e521b3f509fe79e790418a544e8 - RNScreens: c2e3cc506212228c607b4785b315205e28acbf0f + React-utils: fbb79f805d13e0613bc1f799c7bbe5659a0d5ba9 + ReactAppDependencyProvider: e6d0c3c3cc9862a3ccef0c252835cd7ccb96313d + ReactCodegen: 16c2bfcebf870208d7e29ff0c065f4c0fa03034d + ReactCommon: e243aa261effc83c10208f0794bade55ca9ae5b6 + RNFastImage: 462a183c4b0b6b26fdfd639e1ed6ba37536c3b87 + RNScreens: 482e9707f9826230810c92e765751af53826d509 SDWebImage: a7f831e1a65eb5e285e3fb046a23fcfbf08e696d SDWebImageWebPCoder: 908b83b6adda48effe7667cd2b7f78c897e5111d SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748 - Yoga: 645152fcc8359c80cdeb9be7a9e0d2e6c01304a4 + Yoga: 29f74a5b77dca8c37669e1e1e867e5f4e12407df PODFILE CHECKSUM: 8c90c25c7a6bc16ec7b3ed7968df16467ab0fc35 -COCOAPODS: 1.15.2 +COCOAPODS: 1.16.2 diff --git a/example/package.json b/example/package.json index c3b8736d..8acd002f 100644 --- a/example/package.json +++ b/example/package.json @@ -10,6 +10,7 @@ "pods": "bundle install && cd ios && bundle exec pod install" }, "dependencies": { + "@react-native-camera-roll/camera-roll": "^7.10.1", "@react-navigation/bottom-tabs": "^7.3.14", "@react-navigation/native": "^7.1.10", "react": "19.0.0", diff --git a/example/src/App.tsx b/example/src/App.tsx index e31b43fe..008f7ce0 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -5,28 +5,27 @@ * @format */ -import React from 'react'; -import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; -import { FastImageTab } from './FastImageTab'; -import { NitroImageTab } from './NitroImageTab'; -import { createStaticNavigation } from '@react-navigation/native'; -import { EmptyTab } from './EmptyTab'; +import type React from "react"; +import { createBottomTabNavigator } from "@react-navigation/bottom-tabs"; +import { FastImageTab } from "./FastImageTab"; +import { NitroImageTab } from "./NitroImageTab"; +import { createStaticNavigation } from "@react-navigation/native"; +import { EmptyTab } from "./EmptyTab"; +import { NitroMediaLibraryImageTab } from "./NitroMediaLibraryImageTab"; const Tabs = createBottomTabNavigator({ - detachInactiveScreens: false, - screens: { - Empty: EmptyTab, - FastImage: FastImageTab, - NitroImage: NitroImageTab, - }, + detachInactiveScreens: false, + screens: { + Empty: EmptyTab, + FastImage: FastImageTab, + NitroImage: NitroImageTab, + NitroMediaLibraryImage: NitroMediaLibraryImageTab, + }, }); const Navigation = createStaticNavigation(Tabs); function App(): React.JSX.Element { - return ( - - ); + return ; } - export default App; diff --git a/example/src/NitroMediaLibraryImageTab.tsx b/example/src/NitroMediaLibraryImageTab.tsx new file mode 100644 index 00000000..0365fd2e --- /dev/null +++ b/example/src/NitroMediaLibraryImageTab.tsx @@ -0,0 +1,65 @@ +import React, { useEffect, useMemo, useState } from 'react'; +import { FlatList, StyleSheet, Text, View } from 'react-native'; +import { Image, loadImageFromArrayBuffer, loadImageFromAssetAsync, NitroImage } from 'react-native-nitro-image'; +import { createImageURLs } from './createImageURLs'; +import { CameraRoll } from "@react-native-camera-roll/camera-roll"; +import { AsyncImageLoadOptions } from 'react-native-nitro-image/lib/specs/ImageFactory.nitro'; + + +function useAssetImage( + url: string, +): Image | undefined { + const [image, setImage] = useState(undefined) + + useEffect(() => { + const load = async () => { + try { + const i = await loadImageFromAssetAsync(url.replace("ph://", "")) + setImage(i) + } catch (error) { + console.error(`Failed to load image from "${url}"!`, error) + setImage(undefined) + } + } + load() + // `options` is missing from dependencies since it's a reference type that will be constructed each render. + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [url]) + + return image +} +function AsyncImageImpl({ url }: { url: string }): React.ReactNode { + const image = useAssetImage(url); + return ; +} +const AsyncImage = React.memo(AsyncImageImpl); + +export function NitroMediaLibraryImageTab() { + const [imageURLs, setImageURLs] = useState([]); + + + useEffect(() => { + CameraRoll.getPhotos({ first: 100,assetType: "Photos" }).then((res) => { + setImageURLs(res.edges.map((edge) => edge.node.image.uri)); + }); + }, []); + + return ( + NitroMediaLibraryImage Tab + ( + + )} + /> + ); +} + +const styles = StyleSheet.create({ + image: { + width: '25%', + aspectRatio: 1, + }, +}); diff --git a/ios/HybridImageFactory.swift b/ios/HybridImageFactory.swift index e5e75646..85f952dc 100644 --- a/ios/HybridImageFactory.swift +++ b/ios/HybridImageFactory.swift @@ -8,6 +8,7 @@ import Foundation import NitroModules import SDWebImage +import Photos class HybridImageFactory: HybridImageFactorySpec { private let queue = DispatchQueue(label: "image-loader", @@ -29,6 +30,60 @@ class HybridImageFactory: HybridImageFactorySpec { } } + /** + * Load Image from URL + */ + func loadFromAssetAsync(assetId: String) throws -> Promise { + let assets = PHAsset.fetchAssets( + withLocalIdentifiers: [assetId], + options: nil + ) + guard let asset = assets.firstObject else { + throw NSError( + domain: "TurboImageView", + code: 404, + userInfo: [NSLocalizedDescriptionKey: "Asset not found"] + ) + } + + return Promise.async { + return try await withCheckedThrowingContinuation { continuation in + let options = PHImageRequestOptions() + options.version = .current + options.deliveryMode = .highQualityFormat + options.isNetworkAccessAllowed = true + + let size = CGSize(width: 400, height: 400) + + // Request resized image + PHImageManager.default().requestImage( + for: asset, + targetSize: size, + contentMode: .aspectFit, + options: options + ) { (image, info) in + if let error = info?[PHImageErrorKey] as? Error { + continuation.resume(throwing: error) + } else if let image = image, + let imageData = image.pngData() ?? image.jpegData( + compressionQuality: 0.9 + ) { + continuation + .resume(returning: HybridImage(uiImage: image)) + } else { + continuation.resume( + throwing: NSError( + domain: "TurboImageView", + code: 500, + userInfo: [NSLocalizedDescriptionKey: "Failed to load or convert image"] + ) + ) + } + } + } + } + } + /** * Load Image from file path */ diff --git a/nitrogen/generated/android/c++/JHybridImageFactorySpec.cpp b/nitrogen/generated/android/c++/JHybridImageFactorySpec.cpp index abdc8a38..0a0164b2 100644 --- a/nitrogen/generated/android/c++/JHybridImageFactorySpec.cpp +++ b/nitrogen/generated/android/c++/JHybridImageFactorySpec.cpp @@ -69,6 +69,22 @@ namespace margelo::nitro::image { return __promise; }(); } + std::shared_ptr>> JHybridImageFactorySpec::loadFromAssetAsync(const std::string& assetId) { + static const auto method = javaClassStatic()->getMethod(jni::alias_ref /* assetId */)>("loadFromAssetAsync"); + auto __result = method(_javaPart, jni::make_jstring(assetId)); + return [&]() { + auto __promise = Promise>::create(); + __result->cthis()->addOnResolvedListener([=](const jni::alias_ref& __boxedResult) { + auto __result = jni::static_ref_cast(__boxedResult); + __promise->resolve(JNISharedPtr::make_shared_from_jni(jni::make_global(__result))); + }); + __result->cthis()->addOnRejectedListener([=](const jni::alias_ref& __throwable) { + jni::JniException __jniError(__throwable); + __promise->reject(std::make_exception_ptr(__jniError)); + }); + return __promise; + }(); + } std::shared_ptr JHybridImageFactorySpec::loadFromFile(const std::string& filePath) { static const auto method = javaClassStatic()->getMethod(jni::alias_ref /* filePath */)>("loadFromFile"); auto __result = method(_javaPart, jni::make_jstring(filePath)); diff --git a/nitrogen/generated/android/c++/JHybridImageFactorySpec.hpp b/nitrogen/generated/android/c++/JHybridImageFactorySpec.hpp index bf4f73ca..c9e15ad5 100644 --- a/nitrogen/generated/android/c++/JHybridImageFactorySpec.hpp +++ b/nitrogen/generated/android/c++/JHybridImageFactorySpec.hpp @@ -52,6 +52,7 @@ namespace margelo::nitro::image { public: // Methods std::shared_ptr>> loadFromURLAsync(const std::string& url, const std::optional& options) override; + std::shared_ptr>> loadFromAssetAsync(const std::string& assetId) override; std::shared_ptr loadFromFile(const std::string& filePath) override; std::shared_ptr>> loadFromFileAsync(const std::string& filePath) override; std::shared_ptr loadFromResources(const std::string& name) override; diff --git a/nitrogen/generated/android/kotlin/com/margelo/nitro/image/HybridImageFactorySpec.kt b/nitrogen/generated/android/kotlin/com/margelo/nitro/image/HybridImageFactorySpec.kt index e0e1c6eb..94336af2 100644 --- a/nitrogen/generated/android/kotlin/com/margelo/nitro/image/HybridImageFactorySpec.kt +++ b/nitrogen/generated/android/kotlin/com/margelo/nitro/image/HybridImageFactorySpec.kt @@ -44,6 +44,10 @@ abstract class HybridImageFactorySpec: HybridObject() { @Keep abstract fun loadFromURLAsync(url: String, options: AsyncImageLoadOptions?): Promise + @DoNotStrip + @Keep + abstract fun loadFromAssetAsync(assetId: String): Promise + @DoNotStrip @Keep abstract fun loadFromFile(filePath: String): HybridImageSpec diff --git a/nitrogen/generated/ios/c++/HybridImageFactorySpecSwift.hpp b/nitrogen/generated/ios/c++/HybridImageFactorySpecSwift.hpp index 0c961b62..3eb4d0bd 100644 --- a/nitrogen/generated/ios/c++/HybridImageFactorySpecSwift.hpp +++ b/nitrogen/generated/ios/c++/HybridImageFactorySpecSwift.hpp @@ -80,6 +80,14 @@ namespace margelo::nitro::image { auto __value = std::move(__result.value()); return __value; } + inline std::shared_ptr>> loadFromAssetAsync(const std::string& assetId) override { + auto __result = _swiftPart.loadFromAssetAsync(assetId); + if (__result.hasError()) [[unlikely]] { + std::rethrow_exception(__result.error()); + } + auto __value = std::move(__result.value()); + return __value; + } inline std::shared_ptr loadFromFile(const std::string& filePath) override { auto __result = _swiftPart.loadFromFile(filePath); if (__result.hasError()) [[unlikely]] { diff --git a/nitrogen/generated/ios/swift/HybridImageFactorySpec.swift b/nitrogen/generated/ios/swift/HybridImageFactorySpec.swift index ac62f102..7c8ec6b0 100644 --- a/nitrogen/generated/ios/swift/HybridImageFactorySpec.swift +++ b/nitrogen/generated/ios/swift/HybridImageFactorySpec.swift @@ -15,6 +15,7 @@ public protocol HybridImageFactorySpec_protocol: HybridObject { // Methods func loadFromURLAsync(url: String, options: AsyncImageLoadOptions?) throws -> Promise<(any HybridImageSpec)> + func loadFromAssetAsync(assetId: String) throws -> Promise<(any HybridImageSpec)> func loadFromFile(filePath: String) throws -> (any HybridImageSpec) func loadFromFileAsync(filePath: String) throws -> Promise<(any HybridImageSpec)> func loadFromResources(name: String) throws -> (any HybridImageSpec) diff --git a/nitrogen/generated/ios/swift/HybridImageFactorySpec_cxx.swift b/nitrogen/generated/ios/swift/HybridImageFactorySpec_cxx.swift index 1ed03434..4df66082 100644 --- a/nitrogen/generated/ios/swift/HybridImageFactorySpec_cxx.swift +++ b/nitrogen/generated/ios/swift/HybridImageFactorySpec_cxx.swift @@ -128,6 +128,28 @@ public class HybridImageFactorySpec_cxx { } } + @inline(__always) + public final func loadFromAssetAsync(assetId: std.string) -> bridge.Result_std__shared_ptr_Promise_std__shared_ptr_margelo__nitro__image__HybridImageSpec____ { + do { + let __result = try self.__implementation.loadFromAssetAsync(assetId: String(assetId)) + let __resultCpp = { () -> bridge.std__shared_ptr_Promise_std__shared_ptr_margelo__nitro__image__HybridImageSpec___ in + let __promise = bridge.create_std__shared_ptr_Promise_std__shared_ptr_margelo__nitro__image__HybridImageSpec___() + let __promiseHolder = bridge.wrap_std__shared_ptr_Promise_std__shared_ptr_margelo__nitro__image__HybridImageSpec___(__promise) + __result + .then({ __result in __promiseHolder.resolve({ () -> bridge.std__shared_ptr_margelo__nitro__image__HybridImageSpec_ in + let __cxxWrapped = __result.getCxxWrapper() + return __cxxWrapped.getCxxPart() + }()) }) + .catch({ __error in __promiseHolder.reject(__error.toCpp()) }) + return __promise + }() + return bridge.create_Result_std__shared_ptr_Promise_std__shared_ptr_margelo__nitro__image__HybridImageSpec____(__resultCpp) + } catch (let __error) { + let __exceptionPtr = __error.toCpp() + return bridge.create_Result_std__shared_ptr_Promise_std__shared_ptr_margelo__nitro__image__HybridImageSpec____(__exceptionPtr) + } + } + @inline(__always) public final func loadFromFile(filePath: std.string) -> bridge.Result_std__shared_ptr_margelo__nitro__image__HybridImageSpec__ { do { diff --git a/nitrogen/generated/shared/c++/HybridImageFactorySpec.cpp b/nitrogen/generated/shared/c++/HybridImageFactorySpec.cpp index fccf2788..47d5f542 100644 --- a/nitrogen/generated/shared/c++/HybridImageFactorySpec.cpp +++ b/nitrogen/generated/shared/c++/HybridImageFactorySpec.cpp @@ -15,6 +15,7 @@ namespace margelo::nitro::image { // load custom methods/properties registerHybrids(this, [](Prototype& prototype) { prototype.registerHybridMethod("loadFromURLAsync", &HybridImageFactorySpec::loadFromURLAsync); + prototype.registerHybridMethod("loadFromAssetAsync", &HybridImageFactorySpec::loadFromAssetAsync); prototype.registerHybridMethod("loadFromFile", &HybridImageFactorySpec::loadFromFile); prototype.registerHybridMethod("loadFromFileAsync", &HybridImageFactorySpec::loadFromFileAsync); prototype.registerHybridMethod("loadFromResources", &HybridImageFactorySpec::loadFromResources); diff --git a/nitrogen/generated/shared/c++/HybridImageFactorySpec.hpp b/nitrogen/generated/shared/c++/HybridImageFactorySpec.hpp index 05a890c7..f76ffc80 100644 --- a/nitrogen/generated/shared/c++/HybridImageFactorySpec.hpp +++ b/nitrogen/generated/shared/c++/HybridImageFactorySpec.hpp @@ -60,6 +60,7 @@ namespace margelo::nitro::image { public: // Methods virtual std::shared_ptr>> loadFromURLAsync(const std::string& url, const std::optional& options) = 0; + virtual std::shared_ptr>> loadFromAssetAsync(const std::string& assetId) = 0; virtual std::shared_ptr loadFromFile(const std::string& filePath) = 0; virtual std::shared_ptr>> loadFromFileAsync(const std::string& filePath) = 0; virtual std::shared_ptr loadFromResources(const std::string& name) = 0; diff --git a/src/ImageFactory.ts b/src/ImageFactory.ts index dd68f0aa..84e7423f 100644 --- a/src/ImageFactory.ts +++ b/src/ImageFactory.ts @@ -15,6 +15,14 @@ const factory = NitroModules.createHybridObject('ImageFactory') */ export const loadImageFromURLAsync = factory.loadFromURLAsync.bind(factory) +/** + * Asynchronously loads an {@linkcode Image} from the given asset identifier. + * @param name The asset identifier of the image to load. + * @throws If no {@linkcode Image} exists under the given {@linkcode name}. + * @platform iOS 8 + */ +export const loadImageFromAssetAsync = factory.loadFromAssetAsync.bind(factory) + /** * Synchronously loads an {@linkcode Image} from the given {@linkcode filePath}. * @param filePath The file path of the {@linkcode Image}. Must contain a file extension. diff --git a/src/specs/ImageFactory.nitro.ts b/src/specs/ImageFactory.nitro.ts index 6388be82..08d55d8e 100644 --- a/src/specs/ImageFactory.nitro.ts +++ b/src/specs/ImageFactory.nitro.ts @@ -66,6 +66,13 @@ export interface ImageFactory * @throws If the data at the given {@linkcode url} cannot be parsed as an {@linkcode Image}. */ loadFromURLAsync(url: string, options?: AsyncImageLoadOptions): Promise + /** + * Asynchronously loads an {@linkcode Image} from the given asset identifier. + * @param name The asset identifier of the image to load. + * @throws If no {@linkcode Image} exists under the given {@linkcode name}. + * @platform iOS 8 + */ + loadFromAssetAsync(assetId: string): Promise /** * Synchronously loads an {@linkcode Image} from the given {@linkcode filePath}. * @param filePath The file path of the {@linkcode Image}. Must contain a file extension. From 67bee8aab9275bfeb7dd8982f53fec272c23b184 Mon Sep 17 00:00:00 2001 From: bglgwyng Date: Sat, 5 Jul 2025 21:13:32 +0900 Subject: [PATCH 02/24] feat: declare `AssetImageLoadOptions` --- ios/HybridImageFactory.swift | 2 +- nitrogen/generated/android/c++/JAspectFit.hpp | 59 ++++++++++++++ .../android/c++/JAssetImageLoadOptions.hpp | 61 +++++++++++++++ .../android/c++/JHybridImageFactorySpec.cpp | 18 ++++- .../android/c++/JHybridImageFactorySpec.hpp | 2 +- nitrogen/generated/android/c++/JImageSize.hpp | 57 ++++++++++++++ .../com/margelo/nitro/image/AspectFit.kt | 25 ++++++ .../nitro/image/AssetImageLoadOptions.kt | 27 +++++++ .../nitro/image/HybridImageFactorySpec.kt | 2 +- .../com/margelo/nitro/image/ImageSize.kt | 27 +++++++ .../ios/NitroImage-Swift-Cxx-Bridge.hpp | 36 +++++++++ .../ios/NitroImage-Swift-Cxx-Umbrella.hpp | 9 +++ .../ios/c++/HybridImageFactorySpecSwift.hpp | 13 +++- nitrogen/generated/ios/swift/AspectFit.swift | 40 ++++++++++ .../ios/swift/AssetImageLoadOptions.swift | 76 ++++++++++++++++++ .../ios/swift/HybridImageFactorySpec.swift | 2 +- .../swift/HybridImageFactorySpec_cxx.swift | 10 ++- nitrogen/generated/ios/swift/ImageSize.swift | 46 +++++++++++ nitrogen/generated/shared/c++/AspectFit.hpp | 78 +++++++++++++++++++ .../shared/c++/AssetImageLoadOptions.hpp | 78 +++++++++++++++++++ .../shared/c++/HybridImageFactorySpec.hpp | 5 +- nitrogen/generated/shared/c++/ImageSize.hpp | 73 +++++++++++++++++ src/specs/ImageFactory.nitro.ts | 32 +++++++- 23 files changed, 765 insertions(+), 13 deletions(-) create mode 100644 nitrogen/generated/android/c++/JAspectFit.hpp create mode 100644 nitrogen/generated/android/c++/JAssetImageLoadOptions.hpp create mode 100644 nitrogen/generated/android/c++/JImageSize.hpp create mode 100644 nitrogen/generated/android/kotlin/com/margelo/nitro/image/AspectFit.kt create mode 100644 nitrogen/generated/android/kotlin/com/margelo/nitro/image/AssetImageLoadOptions.kt create mode 100644 nitrogen/generated/android/kotlin/com/margelo/nitro/image/ImageSize.kt create mode 100644 nitrogen/generated/ios/swift/AspectFit.swift create mode 100644 nitrogen/generated/ios/swift/AssetImageLoadOptions.swift create mode 100644 nitrogen/generated/ios/swift/ImageSize.swift create mode 100644 nitrogen/generated/shared/c++/AspectFit.hpp create mode 100644 nitrogen/generated/shared/c++/AssetImageLoadOptions.hpp create mode 100644 nitrogen/generated/shared/c++/ImageSize.hpp diff --git a/ios/HybridImageFactory.swift b/ios/HybridImageFactory.swift index 85f952dc..cdcbbe06 100644 --- a/ios/HybridImageFactory.swift +++ b/ios/HybridImageFactory.swift @@ -33,7 +33,7 @@ class HybridImageFactory: HybridImageFactorySpec { /** * Load Image from URL */ - func loadFromAssetAsync(assetId: String) throws -> Promise { + func loadFromAssetAsync(assetId: String, options: AssetImageLoadOptions?) throws -> Promise { let assets = PHAsset.fetchAssets( withLocalIdentifiers: [assetId], options: nil diff --git a/nitrogen/generated/android/c++/JAspectFit.hpp b/nitrogen/generated/android/c++/JAspectFit.hpp new file mode 100644 index 00000000..2f6537c3 --- /dev/null +++ b/nitrogen/generated/android/c++/JAspectFit.hpp @@ -0,0 +1,59 @@ +/// +/// JAspectFit.hpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +#pragma once + +#include +#include "AspectFit.hpp" + +namespace margelo::nitro::image { + + using namespace facebook; + + /** + * The C++ JNI bridge between the C++ enum "AspectFit" and the the Kotlin enum "AspectFit". + */ + struct JAspectFit final: public jni::JavaClass { + public: + static auto constexpr kJavaDescriptor = "Lcom/margelo/nitro/image/AspectFit;"; + + public: + /** + * Convert this Java/Kotlin-based enum to the C++ enum AspectFit. + */ + [[maybe_unused]] + [[nodiscard]] + AspectFit toCpp() const { + static const auto clazz = javaClassStatic(); + static const auto fieldOrdinal = clazz->getField("_ordinal"); + int ordinal = this->getFieldValue(fieldOrdinal); + return static_cast(ordinal); + } + + public: + /** + * Create a Java/Kotlin-based enum with the given C++ enum's value. + */ + [[maybe_unused]] + static jni::alias_ref fromCpp(AspectFit value) { + static const auto clazz = javaClassStatic(); + static const auto fieldFIT = clazz->getStaticField("FIT"); + static const auto fieldFILL = clazz->getStaticField("FILL"); + + switch (value) { + case AspectFit::FIT: + return clazz->getStaticFieldValue(fieldFIT); + case AspectFit::FILL: + return clazz->getStaticFieldValue(fieldFILL); + default: + std::string stringValue = std::to_string(static_cast(value)); + throw std::invalid_argument("Invalid enum value (" + stringValue + "!"); + } + } + }; + +} // namespace margelo::nitro::image diff --git a/nitrogen/generated/android/c++/JAssetImageLoadOptions.hpp b/nitrogen/generated/android/c++/JAssetImageLoadOptions.hpp new file mode 100644 index 00000000..ef43aa37 --- /dev/null +++ b/nitrogen/generated/android/c++/JAssetImageLoadOptions.hpp @@ -0,0 +1,61 @@ +/// +/// JAssetImageLoadOptions.hpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +#pragma once + +#include +#include "AssetImageLoadOptions.hpp" + +#include "AspectFit.hpp" +#include "ImageSize.hpp" +#include "JAspectFit.hpp" +#include "JImageSize.hpp" +#include + +namespace margelo::nitro::image { + + using namespace facebook; + + /** + * The C++ JNI bridge between the C++ struct "AssetImageLoadOptions" and the the Kotlin data class "AssetImageLoadOptions". + */ + struct JAssetImageLoadOptions final: public jni::JavaClass { + public: + static auto constexpr kJavaDescriptor = "Lcom/margelo/nitro/image/AssetImageLoadOptions;"; + + public: + /** + * Convert this Java/Kotlin-based struct to the C++ struct AssetImageLoadOptions by copying all values to C++. + */ + [[maybe_unused]] + [[nodiscard]] + AssetImageLoadOptions toCpp() const { + static const auto clazz = javaClassStatic(); + static const auto fieldSize = clazz->getField("size"); + jni::local_ref size = this->getFieldValue(fieldSize); + static const auto fieldAspectFit = clazz->getField("aspectFit"); + jni::local_ref aspectFit = this->getFieldValue(fieldAspectFit); + return AssetImageLoadOptions( + size != nullptr ? std::make_optional(size->toCpp()) : std::nullopt, + aspectFit != nullptr ? std::make_optional(aspectFit->toCpp()) : std::nullopt + ); + } + + public: + /** + * Create a Java/Kotlin-based struct by copying all values from the given C++ struct to Java. + */ + [[maybe_unused]] + static jni::local_ref fromCpp(const AssetImageLoadOptions& value) { + return newInstance( + value.size.has_value() ? JImageSize::fromCpp(value.size.value()) : nullptr, + value.aspectFit.has_value() ? JAspectFit::fromCpp(value.aspectFit.value()) : nullptr + ); + } + }; + +} // namespace margelo::nitro::image diff --git a/nitrogen/generated/android/c++/JHybridImageFactorySpec.cpp b/nitrogen/generated/android/c++/JHybridImageFactorySpec.cpp index 0a0164b2..930f6a9a 100644 --- a/nitrogen/generated/android/c++/JHybridImageFactorySpec.cpp +++ b/nitrogen/generated/android/c++/JHybridImageFactorySpec.cpp @@ -13,6 +13,12 @@ namespace margelo::nitro::image { class HybridImageSpec; } namespace margelo::nitro::image { struct AsyncImageLoadOptions; } // Forward declaration of `AsyncImagePriority` to properly resolve imports. namespace margelo::nitro::image { enum class AsyncImagePriority; } +// Forward declaration of `AssetImageLoadOptions` to properly resolve imports. +namespace margelo::nitro::image { struct AssetImageLoadOptions; } +// Forward declaration of `ImageSize` to properly resolve imports. +namespace margelo::nitro::image { struct ImageSize; } +// Forward declaration of `AspectFit` to properly resolve imports. +namespace margelo::nitro::image { enum class AspectFit; } // Forward declaration of `ArrayBuffer` to properly resolve imports. namespace NitroModules { class ArrayBuffer; } @@ -28,6 +34,12 @@ namespace NitroModules { class ArrayBuffer; } #include "JAsyncImageLoadOptions.hpp" #include "AsyncImagePriority.hpp" #include "JAsyncImagePriority.hpp" +#include "AssetImageLoadOptions.hpp" +#include "JAssetImageLoadOptions.hpp" +#include "ImageSize.hpp" +#include "JImageSize.hpp" +#include "AspectFit.hpp" +#include "JAspectFit.hpp" #include #include #include @@ -69,9 +81,9 @@ namespace margelo::nitro::image { return __promise; }(); } - std::shared_ptr>> JHybridImageFactorySpec::loadFromAssetAsync(const std::string& assetId) { - static const auto method = javaClassStatic()->getMethod(jni::alias_ref /* assetId */)>("loadFromAssetAsync"); - auto __result = method(_javaPart, jni::make_jstring(assetId)); + std::shared_ptr>> JHybridImageFactorySpec::loadFromAssetAsync(const std::string& assetId, const std::optional& options) { + static const auto method = javaClassStatic()->getMethod(jni::alias_ref /* assetId */, jni::alias_ref /* options */)>("loadFromAssetAsync"); + auto __result = method(_javaPart, jni::make_jstring(assetId), options.has_value() ? JAssetImageLoadOptions::fromCpp(options.value()) : nullptr); return [&]() { auto __promise = Promise>::create(); __result->cthis()->addOnResolvedListener([=](const jni::alias_ref& __boxedResult) { diff --git a/nitrogen/generated/android/c++/JHybridImageFactorySpec.hpp b/nitrogen/generated/android/c++/JHybridImageFactorySpec.hpp index c9e15ad5..2186e1c3 100644 --- a/nitrogen/generated/android/c++/JHybridImageFactorySpec.hpp +++ b/nitrogen/generated/android/c++/JHybridImageFactorySpec.hpp @@ -52,7 +52,7 @@ namespace margelo::nitro::image { public: // Methods std::shared_ptr>> loadFromURLAsync(const std::string& url, const std::optional& options) override; - std::shared_ptr>> loadFromAssetAsync(const std::string& assetId) override; + std::shared_ptr>> loadFromAssetAsync(const std::string& assetId, const std::optional& options) override; std::shared_ptr loadFromFile(const std::string& filePath) override; std::shared_ptr>> loadFromFileAsync(const std::string& filePath) override; std::shared_ptr loadFromResources(const std::string& name) override; diff --git a/nitrogen/generated/android/c++/JImageSize.hpp b/nitrogen/generated/android/c++/JImageSize.hpp new file mode 100644 index 00000000..49ce88f6 --- /dev/null +++ b/nitrogen/generated/android/c++/JImageSize.hpp @@ -0,0 +1,57 @@ +/// +/// JImageSize.hpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +#pragma once + +#include +#include "ImageSize.hpp" + + + +namespace margelo::nitro::image { + + using namespace facebook; + + /** + * The C++ JNI bridge between the C++ struct "ImageSize" and the the Kotlin data class "ImageSize". + */ + struct JImageSize final: public jni::JavaClass { + public: + static auto constexpr kJavaDescriptor = "Lcom/margelo/nitro/image/ImageSize;"; + + public: + /** + * Convert this Java/Kotlin-based struct to the C++ struct ImageSize by copying all values to C++. + */ + [[maybe_unused]] + [[nodiscard]] + ImageSize toCpp() const { + static const auto clazz = javaClassStatic(); + static const auto fieldWidth = clazz->getField("width"); + double width = this->getFieldValue(fieldWidth); + static const auto fieldHeight = clazz->getField("height"); + double height = this->getFieldValue(fieldHeight); + return ImageSize( + width, + height + ); + } + + public: + /** + * Create a Java/Kotlin-based struct by copying all values from the given C++ struct to Java. + */ + [[maybe_unused]] + static jni::local_ref fromCpp(const ImageSize& value) { + return newInstance( + value.width, + value.height + ); + } + }; + +} // namespace margelo::nitro::image diff --git a/nitrogen/generated/android/kotlin/com/margelo/nitro/image/AspectFit.kt b/nitrogen/generated/android/kotlin/com/margelo/nitro/image/AspectFit.kt new file mode 100644 index 00000000..99a64459 --- /dev/null +++ b/nitrogen/generated/android/kotlin/com/margelo/nitro/image/AspectFit.kt @@ -0,0 +1,25 @@ +/// +/// AspectFit.kt +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +package com.margelo.nitro.image + +import androidx.annotation.Keep +import com.facebook.proguard.annotations.DoNotStrip + +/** + * Represents the JavaScript enum/union "AspectFit". + */ +@DoNotStrip +@Keep +enum class AspectFit { + FIT, + FILL; + + @DoNotStrip + @Keep + private val _ordinal = ordinal +} diff --git a/nitrogen/generated/android/kotlin/com/margelo/nitro/image/AssetImageLoadOptions.kt b/nitrogen/generated/android/kotlin/com/margelo/nitro/image/AssetImageLoadOptions.kt new file mode 100644 index 00000000..3a5a4d09 --- /dev/null +++ b/nitrogen/generated/android/kotlin/com/margelo/nitro/image/AssetImageLoadOptions.kt @@ -0,0 +1,27 @@ +/// +/// AssetImageLoadOptions.kt +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +package com.margelo.nitro.image + +import androidx.annotation.Keep +import com.facebook.proguard.annotations.DoNotStrip +import com.margelo.nitro.core.* + +/** + * Represents the JavaScript object/struct "AssetImageLoadOptions". + */ +@DoNotStrip +@Keep +data class AssetImageLoadOptions + @DoNotStrip + @Keep + constructor( + val size: ImageSize?, + val aspectFit: AspectFit? + ) { + /* main constructor */ +} diff --git a/nitrogen/generated/android/kotlin/com/margelo/nitro/image/HybridImageFactorySpec.kt b/nitrogen/generated/android/kotlin/com/margelo/nitro/image/HybridImageFactorySpec.kt index 94336af2..fc4f641e 100644 --- a/nitrogen/generated/android/kotlin/com/margelo/nitro/image/HybridImageFactorySpec.kt +++ b/nitrogen/generated/android/kotlin/com/margelo/nitro/image/HybridImageFactorySpec.kt @@ -46,7 +46,7 @@ abstract class HybridImageFactorySpec: HybridObject() { @DoNotStrip @Keep - abstract fun loadFromAssetAsync(assetId: String): Promise + abstract fun loadFromAssetAsync(assetId: String, options: AssetImageLoadOptions?): Promise @DoNotStrip @Keep diff --git a/nitrogen/generated/android/kotlin/com/margelo/nitro/image/ImageSize.kt b/nitrogen/generated/android/kotlin/com/margelo/nitro/image/ImageSize.kt new file mode 100644 index 00000000..2b70d2a9 --- /dev/null +++ b/nitrogen/generated/android/kotlin/com/margelo/nitro/image/ImageSize.kt @@ -0,0 +1,27 @@ +/// +/// ImageSize.kt +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +package com.margelo.nitro.image + +import androidx.annotation.Keep +import com.facebook.proguard.annotations.DoNotStrip +import com.margelo.nitro.core.* + +/** + * Represents the JavaScript object/struct "ImageSize". + */ +@DoNotStrip +@Keep +data class ImageSize + @DoNotStrip + @Keep + constructor( + val width: Double, + val height: Double + ) { + /* main constructor */ +} diff --git a/nitrogen/generated/ios/NitroImage-Swift-Cxx-Bridge.hpp b/nitrogen/generated/ios/NitroImage-Swift-Cxx-Bridge.hpp index 07d170a7..f00f62f4 100644 --- a/nitrogen/generated/ios/NitroImage-Swift-Cxx-Bridge.hpp +++ b/nitrogen/generated/ios/NitroImage-Swift-Cxx-Bridge.hpp @@ -12,6 +12,10 @@ namespace NitroModules { class ArrayBufferHolder; } // Forward declaration of `ArrayBuffer` to properly resolve imports. namespace NitroModules { class ArrayBuffer; } +// Forward declaration of `AspectFit` to properly resolve imports. +namespace margelo::nitro::image { enum class AspectFit; } +// Forward declaration of `AssetImageLoadOptions` to properly resolve imports. +namespace margelo::nitro::image { struct AssetImageLoadOptions; } // Forward declaration of `AsyncImageLoadOptions` to properly resolve imports. namespace margelo::nitro::image { struct AsyncImageLoadOptions; } // Forward declaration of `AsyncImagePriority` to properly resolve imports. @@ -24,6 +28,8 @@ namespace margelo::nitro::image { class HybridImageSpec; } namespace margelo::nitro::image { class HybridImageUtilsSpec; } // Forward declaration of `HybridNitroImageViewSpec` to properly resolve imports. namespace margelo::nitro::image { class HybridNitroImageViewSpec; } +// Forward declaration of `ImageSize` to properly resolve imports. +namespace margelo::nitro::image { struct ImageSize; } // Forward declarations of Swift defined types // Forward declaration of `HybridImageFactorySpec_cxx` to properly resolve imports. @@ -36,12 +42,15 @@ namespace NitroImage { class HybridImageUtilsSpec_cxx; } namespace NitroImage { class HybridNitroImageViewSpec_cxx; } // Include C++ defined types +#include "AspectFit.hpp" +#include "AssetImageLoadOptions.hpp" #include "AsyncImageLoadOptions.hpp" #include "AsyncImagePriority.hpp" #include "HybridImageFactorySpec.hpp" #include "HybridImageSpec.hpp" #include "HybridImageUtilsSpec.hpp" #include "HybridNitroImageViewSpec.hpp" +#include "ImageSize.hpp" #include #include #include @@ -310,6 +319,33 @@ namespace margelo::nitro::image::bridge::swift { return std::optional(value); } + // pragma MARK: std::optional + /** + * Specialized version of `std::optional`. + */ + using std__optional_ImageSize_ = std::optional; + inline std::optional create_std__optional_ImageSize_(const ImageSize& value) { + return std::optional(value); + } + + // pragma MARK: std::optional + /** + * Specialized version of `std::optional`. + */ + using std__optional_AspectFit_ = std::optional; + inline std::optional create_std__optional_AspectFit_(const AspectFit& value) { + return std::optional(value); + } + + // pragma MARK: std::optional + /** + * Specialized version of `std::optional`. + */ + using std__optional_AssetImageLoadOptions_ = std::optional; + inline std::optional create_std__optional_AssetImageLoadOptions_(const AssetImageLoadOptions& value) { + return std::optional(value); + } + // pragma MARK: std::shared_ptr /** * Specialized version of `std::shared_ptr`. diff --git a/nitrogen/generated/ios/NitroImage-Swift-Cxx-Umbrella.hpp b/nitrogen/generated/ios/NitroImage-Swift-Cxx-Umbrella.hpp index 87f461e7..2cdeeb06 100644 --- a/nitrogen/generated/ios/NitroImage-Swift-Cxx-Umbrella.hpp +++ b/nitrogen/generated/ios/NitroImage-Swift-Cxx-Umbrella.hpp @@ -10,6 +10,10 @@ // Forward declarations of C++ defined types // Forward declaration of `ArrayBuffer` to properly resolve imports. namespace NitroModules { class ArrayBuffer; } +// Forward declaration of `AspectFit` to properly resolve imports. +namespace margelo::nitro::image { enum class AspectFit; } +// Forward declaration of `AssetImageLoadOptions` to properly resolve imports. +namespace margelo::nitro::image { struct AssetImageLoadOptions; } // Forward declaration of `AsyncImageLoadOptions` to properly resolve imports. namespace margelo::nitro::image { struct AsyncImageLoadOptions; } // Forward declaration of `AsyncImagePriority` to properly resolve imports. @@ -24,8 +28,12 @@ namespace margelo::nitro::image { class HybridImageUtilsSpec; } namespace margelo::nitro::image { class HybridNitroImageViewSpec; } // Forward declaration of `ImageFormat` to properly resolve imports. namespace margelo::nitro::image { enum class ImageFormat; } +// Forward declaration of `ImageSize` to properly resolve imports. +namespace margelo::nitro::image { struct ImageSize; } // Include C++ defined types +#include "AspectFit.hpp" +#include "AssetImageLoadOptions.hpp" #include "AsyncImageLoadOptions.hpp" #include "AsyncImagePriority.hpp" #include "HybridImageFactorySpec.hpp" @@ -33,6 +41,7 @@ namespace margelo::nitro::image { enum class ImageFormat; } #include "HybridImageUtilsSpec.hpp" #include "HybridNitroImageViewSpec.hpp" #include "ImageFormat.hpp" +#include "ImageSize.hpp" #include #include #include diff --git a/nitrogen/generated/ios/c++/HybridImageFactorySpecSwift.hpp b/nitrogen/generated/ios/c++/HybridImageFactorySpecSwift.hpp index 3eb4d0bd..e5bf8374 100644 --- a/nitrogen/generated/ios/c++/HybridImageFactorySpecSwift.hpp +++ b/nitrogen/generated/ios/c++/HybridImageFactorySpecSwift.hpp @@ -18,6 +18,12 @@ namespace margelo::nitro::image { class HybridImageSpec; } namespace margelo::nitro::image { struct AsyncImageLoadOptions; } // Forward declaration of `AsyncImagePriority` to properly resolve imports. namespace margelo::nitro::image { enum class AsyncImagePriority; } +// Forward declaration of `AssetImageLoadOptions` to properly resolve imports. +namespace margelo::nitro::image { struct AssetImageLoadOptions; } +// Forward declaration of `ImageSize` to properly resolve imports. +namespace margelo::nitro::image { struct ImageSize; } +// Forward declaration of `AspectFit` to properly resolve imports. +namespace margelo::nitro::image { enum class AspectFit; } // Forward declaration of `ArrayBuffer` to properly resolve imports. namespace NitroModules { class ArrayBuffer; } // Forward declaration of `ArrayBufferHolder` to properly resolve imports. @@ -30,6 +36,9 @@ namespace NitroModules { class ArrayBufferHolder; } #include #include "AsyncImageLoadOptions.hpp" #include "AsyncImagePriority.hpp" +#include "AssetImageLoadOptions.hpp" +#include "ImageSize.hpp" +#include "AspectFit.hpp" #include #include @@ -80,8 +89,8 @@ namespace margelo::nitro::image { auto __value = std::move(__result.value()); return __value; } - inline std::shared_ptr>> loadFromAssetAsync(const std::string& assetId) override { - auto __result = _swiftPart.loadFromAssetAsync(assetId); + inline std::shared_ptr>> loadFromAssetAsync(const std::string& assetId, const std::optional& options) override { + auto __result = _swiftPart.loadFromAssetAsync(assetId, options); if (__result.hasError()) [[unlikely]] { std::rethrow_exception(__result.error()); } diff --git a/nitrogen/generated/ios/swift/AspectFit.swift b/nitrogen/generated/ios/swift/AspectFit.swift new file mode 100644 index 00000000..cf3acc57 --- /dev/null +++ b/nitrogen/generated/ios/swift/AspectFit.swift @@ -0,0 +1,40 @@ +/// +/// AspectFit.swift +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +/** + * Represents the JS union `AspectFit`, backed by a C++ enum. + */ +public typealias AspectFit = margelo.nitro.image.AspectFit + +public extension AspectFit { + /** + * Get a AspectFit for the given String value, or + * return `nil` if the given value was invalid/unknown. + */ + init?(fromString string: String) { + switch string { + case "fit": + self = .fit + case "fill": + self = .fill + default: + return nil + } + } + + /** + * Get the String value this AspectFit represents. + */ + var stringValue: String { + switch self { + case .fit: + return "fit" + case .fill: + return "fill" + } + } +} diff --git a/nitrogen/generated/ios/swift/AssetImageLoadOptions.swift b/nitrogen/generated/ios/swift/AssetImageLoadOptions.swift new file mode 100644 index 00000000..ed4d2ad1 --- /dev/null +++ b/nitrogen/generated/ios/swift/AssetImageLoadOptions.swift @@ -0,0 +1,76 @@ +/// +/// AssetImageLoadOptions.swift +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +import NitroModules + +/** + * Represents an instance of `AssetImageLoadOptions`, backed by a C++ struct. + */ +public typealias AssetImageLoadOptions = margelo.nitro.image.AssetImageLoadOptions + +public extension AssetImageLoadOptions { + private typealias bridge = margelo.nitro.image.bridge.swift + + /** + * Create a new instance of `AssetImageLoadOptions`. + */ + init(size: ImageSize?, aspectFit: AspectFit?) { + self.init({ () -> bridge.std__optional_ImageSize_ in + if let __unwrappedValue = size { + return bridge.create_std__optional_ImageSize_(__unwrappedValue) + } else { + return .init() + } + }(), { () -> bridge.std__optional_AspectFit_ in + if let __unwrappedValue = aspectFit { + return bridge.create_std__optional_AspectFit_(__unwrappedValue) + } else { + return .init() + } + }()) + } + + var size: ImageSize? { + @inline(__always) + get { + return { () -> ImageSize? in + if let __unwrapped = self.__size.value { + return __unwrapped + } else { + return nil + } + }() + } + @inline(__always) + set { + self.__size = { () -> bridge.std__optional_ImageSize_ in + if let __unwrappedValue = newValue { + return bridge.create_std__optional_ImageSize_(__unwrappedValue) + } else { + return .init() + } + }() + } + } + + var aspectFit: AspectFit? { + @inline(__always) + get { + return self.__aspectFit.value + } + @inline(__always) + set { + self.__aspectFit = { () -> bridge.std__optional_AspectFit_ in + if let __unwrappedValue = newValue { + return bridge.create_std__optional_AspectFit_(__unwrappedValue) + } else { + return .init() + } + }() + } + } +} diff --git a/nitrogen/generated/ios/swift/HybridImageFactorySpec.swift b/nitrogen/generated/ios/swift/HybridImageFactorySpec.swift index 7c8ec6b0..bba2466d 100644 --- a/nitrogen/generated/ios/swift/HybridImageFactorySpec.swift +++ b/nitrogen/generated/ios/swift/HybridImageFactorySpec.swift @@ -15,7 +15,7 @@ public protocol HybridImageFactorySpec_protocol: HybridObject { // Methods func loadFromURLAsync(url: String, options: AsyncImageLoadOptions?) throws -> Promise<(any HybridImageSpec)> - func loadFromAssetAsync(assetId: String) throws -> Promise<(any HybridImageSpec)> + func loadFromAssetAsync(assetId: String, options: AssetImageLoadOptions?) throws -> Promise<(any HybridImageSpec)> func loadFromFile(filePath: String) throws -> (any HybridImageSpec) func loadFromFileAsync(filePath: String) throws -> Promise<(any HybridImageSpec)> func loadFromResources(name: String) throws -> (any HybridImageSpec) diff --git a/nitrogen/generated/ios/swift/HybridImageFactorySpec_cxx.swift b/nitrogen/generated/ios/swift/HybridImageFactorySpec_cxx.swift index 4df66082..79c1c32d 100644 --- a/nitrogen/generated/ios/swift/HybridImageFactorySpec_cxx.swift +++ b/nitrogen/generated/ios/swift/HybridImageFactorySpec_cxx.swift @@ -129,9 +129,15 @@ public class HybridImageFactorySpec_cxx { } @inline(__always) - public final func loadFromAssetAsync(assetId: std.string) -> bridge.Result_std__shared_ptr_Promise_std__shared_ptr_margelo__nitro__image__HybridImageSpec____ { + public final func loadFromAssetAsync(assetId: std.string, options: bridge.std__optional_AssetImageLoadOptions_) -> bridge.Result_std__shared_ptr_Promise_std__shared_ptr_margelo__nitro__image__HybridImageSpec____ { do { - let __result = try self.__implementation.loadFromAssetAsync(assetId: String(assetId)) + let __result = try self.__implementation.loadFromAssetAsync(assetId: String(assetId), options: { () -> AssetImageLoadOptions? in + if let __unwrapped = options.value { + return __unwrapped + } else { + return nil + } + }()) let __resultCpp = { () -> bridge.std__shared_ptr_Promise_std__shared_ptr_margelo__nitro__image__HybridImageSpec___ in let __promise = bridge.create_std__shared_ptr_Promise_std__shared_ptr_margelo__nitro__image__HybridImageSpec___() let __promiseHolder = bridge.wrap_std__shared_ptr_Promise_std__shared_ptr_margelo__nitro__image__HybridImageSpec___(__promise) diff --git a/nitrogen/generated/ios/swift/ImageSize.swift b/nitrogen/generated/ios/swift/ImageSize.swift new file mode 100644 index 00000000..7cd82dd0 --- /dev/null +++ b/nitrogen/generated/ios/swift/ImageSize.swift @@ -0,0 +1,46 @@ +/// +/// ImageSize.swift +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +import NitroModules + +/** + * Represents an instance of `ImageSize`, backed by a C++ struct. + */ +public typealias ImageSize = margelo.nitro.image.ImageSize + +public extension ImageSize { + private typealias bridge = margelo.nitro.image.bridge.swift + + /** + * Create a new instance of `ImageSize`. + */ + init(width: Double, height: Double) { + self.init(width, height) + } + + var width: Double { + @inline(__always) + get { + return self.__width + } + @inline(__always) + set { + self.__width = newValue + } + } + + var height: Double { + @inline(__always) + get { + return self.__height + } + @inline(__always) + set { + self.__height = newValue + } + } +} diff --git a/nitrogen/generated/shared/c++/AspectFit.hpp b/nitrogen/generated/shared/c++/AspectFit.hpp new file mode 100644 index 00000000..b5da4abd --- /dev/null +++ b/nitrogen/generated/shared/c++/AspectFit.hpp @@ -0,0 +1,78 @@ +/// +/// AspectFit.hpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +#pragma once + +#if __has_include() +#include +#else +#error NitroModules cannot be found! Are you sure you installed NitroModules properly? +#endif +#if __has_include() +#include +#else +#error NitroModules cannot be found! Are you sure you installed NitroModules properly? +#endif +#if __has_include() +#include +#else +#error NitroModules cannot be found! Are you sure you installed NitroModules properly? +#endif + +namespace margelo::nitro::image { + + /** + * An enum which can be represented as a JavaScript union (AspectFit). + */ + enum class AspectFit { + FIT SWIFT_NAME(fit) = 0, + FILL SWIFT_NAME(fill) = 1, + } CLOSED_ENUM; + +} // namespace margelo::nitro::image + +namespace margelo::nitro { + + using namespace margelo::nitro::image; + + // C++ AspectFit <> JS AspectFit (union) + template <> + struct JSIConverter final { + static inline AspectFit fromJSI(jsi::Runtime& runtime, const jsi::Value& arg) { + std::string unionValue = JSIConverter::fromJSI(runtime, arg); + switch (hashString(unionValue.c_str(), unionValue.size())) { + case hashString("fit"): return AspectFit::FIT; + case hashString("fill"): return AspectFit::FILL; + default: [[unlikely]] + throw std::invalid_argument("Cannot convert \"" + unionValue + "\" to enum AspectFit - invalid value!"); + } + } + static inline jsi::Value toJSI(jsi::Runtime& runtime, AspectFit arg) { + switch (arg) { + case AspectFit::FIT: return JSIConverter::toJSI(runtime, "fit"); + case AspectFit::FILL: return JSIConverter::toJSI(runtime, "fill"); + default: [[unlikely]] + throw std::invalid_argument("Cannot convert AspectFit to JS - invalid value: " + + std::to_string(static_cast(arg)) + "!"); + } + } + static inline bool canConvert(jsi::Runtime& runtime, const jsi::Value& value) { + if (!value.isString()) { + return false; + } + std::string unionValue = JSIConverter::fromJSI(runtime, value); + switch (hashString(unionValue.c_str(), unionValue.size())) { + case hashString("fit"): + case hashString("fill"): + return true; + default: + return false; + } + } + }; + +} // namespace margelo::nitro diff --git a/nitrogen/generated/shared/c++/AssetImageLoadOptions.hpp b/nitrogen/generated/shared/c++/AssetImageLoadOptions.hpp new file mode 100644 index 00000000..9f1385e6 --- /dev/null +++ b/nitrogen/generated/shared/c++/AssetImageLoadOptions.hpp @@ -0,0 +1,78 @@ +/// +/// AssetImageLoadOptions.hpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +#pragma once + +#if __has_include() +#include +#else +#error NitroModules cannot be found! Are you sure you installed NitroModules properly? +#endif +#if __has_include() +#include +#else +#error NitroModules cannot be found! Are you sure you installed NitroModules properly? +#endif + +// Forward declaration of `ImageSize` to properly resolve imports. +namespace margelo::nitro::image { struct ImageSize; } +// Forward declaration of `AspectFit` to properly resolve imports. +namespace margelo::nitro::image { enum class AspectFit; } + +#include +#include "ImageSize.hpp" +#include "AspectFit.hpp" + +namespace margelo::nitro::image { + + /** + * A struct which can be represented as a JavaScript object (AssetImageLoadOptions). + */ + struct AssetImageLoadOptions { + public: + std::optional size SWIFT_PRIVATE; + std::optional aspectFit SWIFT_PRIVATE; + + public: + AssetImageLoadOptions() = default; + explicit AssetImageLoadOptions(std::optional size, std::optional aspectFit): size(size), aspectFit(aspectFit) {} + }; + +} // namespace margelo::nitro::image + +namespace margelo::nitro { + + using namespace margelo::nitro::image; + + // C++ AssetImageLoadOptions <> JS AssetImageLoadOptions (object) + template <> + struct JSIConverter final { + static inline AssetImageLoadOptions fromJSI(jsi::Runtime& runtime, const jsi::Value& arg) { + jsi::Object obj = arg.asObject(runtime); + return AssetImageLoadOptions( + JSIConverter>::fromJSI(runtime, obj.getProperty(runtime, "size")), + JSIConverter>::fromJSI(runtime, obj.getProperty(runtime, "aspectFit")) + ); + } + static inline jsi::Value toJSI(jsi::Runtime& runtime, const AssetImageLoadOptions& arg) { + jsi::Object obj(runtime); + obj.setProperty(runtime, "size", JSIConverter>::toJSI(runtime, arg.size)); + obj.setProperty(runtime, "aspectFit", JSIConverter>::toJSI(runtime, arg.aspectFit)); + return obj; + } + static inline bool canConvert(jsi::Runtime& runtime, const jsi::Value& value) { + if (!value.isObject()) { + return false; + } + jsi::Object obj = value.getObject(runtime); + if (!JSIConverter>::canConvert(runtime, obj.getProperty(runtime, "size"))) return false; + if (!JSIConverter>::canConvert(runtime, obj.getProperty(runtime, "aspectFit"))) return false; + return true; + } + }; + +} // namespace margelo::nitro diff --git a/nitrogen/generated/shared/c++/HybridImageFactorySpec.hpp b/nitrogen/generated/shared/c++/HybridImageFactorySpec.hpp index f76ffc80..1f2b4db1 100644 --- a/nitrogen/generated/shared/c++/HybridImageFactorySpec.hpp +++ b/nitrogen/generated/shared/c++/HybridImageFactorySpec.hpp @@ -17,6 +17,8 @@ namespace margelo::nitro::image { class HybridImageSpec; } // Forward declaration of `AsyncImageLoadOptions` to properly resolve imports. namespace margelo::nitro::image { struct AsyncImageLoadOptions; } +// Forward declaration of `AssetImageLoadOptions` to properly resolve imports. +namespace margelo::nitro::image { struct AssetImageLoadOptions; } // Forward declaration of `ArrayBuffer` to properly resolve imports. namespace NitroModules { class ArrayBuffer; } @@ -26,6 +28,7 @@ namespace NitroModules { class ArrayBuffer; } #include #include #include "AsyncImageLoadOptions.hpp" +#include "AssetImageLoadOptions.hpp" #include namespace margelo::nitro::image { @@ -60,7 +63,7 @@ namespace margelo::nitro::image { public: // Methods virtual std::shared_ptr>> loadFromURLAsync(const std::string& url, const std::optional& options) = 0; - virtual std::shared_ptr>> loadFromAssetAsync(const std::string& assetId) = 0; + virtual std::shared_ptr>> loadFromAssetAsync(const std::string& assetId, const std::optional& options) = 0; virtual std::shared_ptr loadFromFile(const std::string& filePath) = 0; virtual std::shared_ptr>> loadFromFileAsync(const std::string& filePath) = 0; virtual std::shared_ptr loadFromResources(const std::string& name) = 0; diff --git a/nitrogen/generated/shared/c++/ImageSize.hpp b/nitrogen/generated/shared/c++/ImageSize.hpp new file mode 100644 index 00000000..0982099c --- /dev/null +++ b/nitrogen/generated/shared/c++/ImageSize.hpp @@ -0,0 +1,73 @@ +/// +/// ImageSize.hpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +#pragma once + +#if __has_include() +#include +#else +#error NitroModules cannot be found! Are you sure you installed NitroModules properly? +#endif +#if __has_include() +#include +#else +#error NitroModules cannot be found! Are you sure you installed NitroModules properly? +#endif + + + + + +namespace margelo::nitro::image { + + /** + * A struct which can be represented as a JavaScript object (ImageSize). + */ + struct ImageSize { + public: + double width SWIFT_PRIVATE; + double height SWIFT_PRIVATE; + + public: + ImageSize() = default; + explicit ImageSize(double width, double height): width(width), height(height) {} + }; + +} // namespace margelo::nitro::image + +namespace margelo::nitro { + + using namespace margelo::nitro::image; + + // C++ ImageSize <> JS ImageSize (object) + template <> + struct JSIConverter final { + static inline ImageSize fromJSI(jsi::Runtime& runtime, const jsi::Value& arg) { + jsi::Object obj = arg.asObject(runtime); + return ImageSize( + JSIConverter::fromJSI(runtime, obj.getProperty(runtime, "width")), + JSIConverter::fromJSI(runtime, obj.getProperty(runtime, "height")) + ); + } + static inline jsi::Value toJSI(jsi::Runtime& runtime, const ImageSize& arg) { + jsi::Object obj(runtime); + obj.setProperty(runtime, "width", JSIConverter::toJSI(runtime, arg.width)); + obj.setProperty(runtime, "height", JSIConverter::toJSI(runtime, arg.height)); + return obj; + } + static inline bool canConvert(jsi::Runtime& runtime, const jsi::Value& value) { + if (!value.isObject()) { + return false; + } + jsi::Object obj = value.getObject(runtime); + if (!JSIConverter::canConvert(runtime, obj.getProperty(runtime, "width"))) return false; + if (!JSIConverter::canConvert(runtime, obj.getProperty(runtime, "height"))) return false; + return true; + } + }; + +} // namespace margelo::nitro diff --git a/src/specs/ImageFactory.nitro.ts b/src/specs/ImageFactory.nitro.ts index 08d55d8e..fcf5f029 100644 --- a/src/specs/ImageFactory.nitro.ts +++ b/src/specs/ImageFactory.nitro.ts @@ -56,6 +56,33 @@ export interface AsyncImageLoadOptions { decodeImage?: boolean } + +export interface ImageSize { + /** + * The width of the image. + */ + width: number + /** + * The height of the image. + */ + height: number +} + +export type AspectFit = 'fit' | 'fill' + +export interface AssetImageLoadOptions { + /** + * Specifies the size of the image. + */ + size?: ImageSize + + /** + * Specifies the aspect fit of the image. + * @default 'fit' + */ + aspectFit?: AspectFit +} + export interface ImageFactory extends HybridObject<{ ios: 'swift'; android: 'kotlin' }> { /** @@ -72,7 +99,10 @@ export interface ImageFactory * @throws If no {@linkcode Image} exists under the given {@linkcode name}. * @platform iOS 8 */ - loadFromAssetAsync(assetId: string): Promise + loadFromAssetAsync( + assetId: string, + options?: AssetImageLoadOptions + ): Promise /** * Synchronously loads an {@linkcode Image} from the given {@linkcode filePath}. * @param filePath The file path of the {@linkcode Image}. Must contain a file extension. From d4fed55d1bf7bdb0045fce4b5dacf37da2e6ea07 Mon Sep 17 00:00:00 2001 From: bglgwyng Date: Tue, 8 Jul 2025 17:06:28 +0900 Subject: [PATCH 03/24] feat: add support for custom image sizing and orientation in media library loading --- example/src/NitroMediaLibraryImageTab.tsx | 6 +- ios/HybridImageFactory.swift | 96 ++++++++++++++++------- 2 files changed, 71 insertions(+), 31 deletions(-) diff --git a/example/src/NitroMediaLibraryImageTab.tsx b/example/src/NitroMediaLibraryImageTab.tsx index 0365fd2e..f83b4bbd 100644 --- a/example/src/NitroMediaLibraryImageTab.tsx +++ b/example/src/NitroMediaLibraryImageTab.tsx @@ -1,9 +1,7 @@ -import React, { useEffect, useMemo, useState } from 'react'; +import React, { useEffect, useState } from 'react'; import { FlatList, StyleSheet, Text, View } from 'react-native'; -import { Image, loadImageFromArrayBuffer, loadImageFromAssetAsync, NitroImage } from 'react-native-nitro-image'; -import { createImageURLs } from './createImageURLs'; +import { Image, loadImageFromAssetAsync, NitroImage } from 'react-native-nitro-image'; import { CameraRoll } from "@react-native-camera-roll/camera-roll"; -import { AsyncImageLoadOptions } from 'react-native-nitro-image/lib/specs/ImageFactory.nitro'; function useAssetImage( diff --git a/ios/HybridImageFactory.swift b/ios/HybridImageFactory.swift index cdcbbe06..27624903 100644 --- a/ios/HybridImageFactory.swift +++ b/ios/HybridImageFactory.swift @@ -22,7 +22,7 @@ class HybridImageFactory: HybridImageFactorySpec { guard let url = URL(string: urlString) else { throw RuntimeError.error(withMessage: "URL string \"\(urlString)\" is not a valid URL!") } - + return Promise.async { let webImageOptions = options?.toSDWebImageOptions() ?? [] let uiImage = try await SDWebImageManager.shared.loadImage(with: url, options: webImageOptions) @@ -48,36 +48,78 @@ class HybridImageFactory: HybridImageFactorySpec { return Promise.async { return try await withCheckedThrowingContinuation { continuation in - let options = PHImageRequestOptions() - options.version = .current - options.deliveryMode = .highQualityFormat - options.isNetworkAccessAllowed = true + let requestOptions = PHImageRequestOptions() + requestOptions.version = .current + requestOptions.deliveryMode = .highQualityFormat + requestOptions.isNetworkAccessAllowed = true - let size = CGSize(width: 400, height: 400) - // Request resized image - PHImageManager.default().requestImage( - for: asset, - targetSize: size, - contentMode: .aspectFit, - options: options - ) { (image, info) in - if let error = info?[PHImageErrorKey] as? Error { - continuation.resume(throwing: error) - } else if let image = image, - let imageData = image.pngData() ?? image.jpegData( - compressionQuality: 0.9 - ) { - continuation - .resume(returning: HybridImage(uiImage: image)) + if let size = options?.size { + let contentMode: PHImageContentMode + if let aspectFit = options?.aspectFit { + switch aspectFit { + case .fit: + contentMode = .aspectFit + case .fill: + contentMode = .aspectFill + } } else { - continuation.resume( - throwing: NSError( - domain: "TurboImageView", - code: 500, - userInfo: [NSLocalizedDescriptionKey: "Failed to load or convert image"] + contentMode = .default + } + + PHImageManager.default().requestImage( + for: asset, + targetSize: CGSize(width: size.width, height: size.height), + contentMode: contentMode, + options: requestOptions + ) { (image, info) in + if let error = info?[PHImageErrorKey] as? Error { + continuation.resume(throwing: error) + } else if let image = image { + continuation + .resume(returning: HybridImage(uiImage: image)) + } else { + continuation.resume( + throwing: NSError( + domain: "TurboImageView", + code: 500, + userInfo: [NSLocalizedDescriptionKey: "Failed to load or convert image"] + ) + ) + } + } + } else { + PHImageManager.default().requestImageDataAndOrientation( + for: asset, + options: requestOptions + ) { (imageData, dataUTI, orientation, info) in + + if let error = info?[PHImageErrorKey] as? Error { + continuation.resume(throwing: error) + } else if let imageData = imageData, + let cgImage = UIImage(data:imageData)?.cgImage { + let uiImageOrientation: UIImage.Orientation + switch orientation { + case .up: uiImageOrientation = .up + case .upMirrored: uiImageOrientation = .upMirrored + case .down: uiImageOrientation = .down + case .downMirrored: uiImageOrientation = .downMirrored + case .left: uiImageOrientation = .left + case .leftMirrored: uiImageOrientation = .leftMirrored + case .right: uiImageOrientation = .right + case .rightMirrored: uiImageOrientation = .rightMirrored + } + let uiImage = UIImage(cgImage: cgImage, scale: 1, orientation:uiImageOrientation ) + continuation.resume(returning: HybridImage(uiImage: uiImage)) + } else { + continuation.resume( + throwing: NSError( + domain: "TurboImageView", + code: 500, + userInfo: [NSLocalizedDescriptionKey: "Failed to load or convert image"] + ) ) - ) + } } } } From 8b376d60eda3c498d23cdd4ce49f60aa6ce69fb5 Mon Sep 17 00:00:00 2001 From: bglgwyng Date: Tue, 8 Jul 2025 17:09:42 +0900 Subject: [PATCH 04/24] refactor: standardize error handling in `HybridImageFactory` --- ios/HybridImageFactory.swift | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/ios/HybridImageFactory.swift b/ios/HybridImageFactory.swift index 27624903..245a86c3 100644 --- a/ios/HybridImageFactory.swift +++ b/ios/HybridImageFactory.swift @@ -80,11 +80,7 @@ class HybridImageFactory: HybridImageFactorySpec { .resume(returning: HybridImage(uiImage: image)) } else { continuation.resume( - throwing: NSError( - domain: "TurboImageView", - code: 500, - userInfo: [NSLocalizedDescriptionKey: "Failed to load or convert image"] - ) + throwing: RuntimeError.error(withMessage: "Failed to load or convert image") ) } } @@ -113,11 +109,7 @@ class HybridImageFactory: HybridImageFactorySpec { continuation.resume(returning: HybridImage(uiImage: uiImage)) } else { continuation.resume( - throwing: NSError( - domain: "TurboImageView", - code: 500, - userInfo: [NSLocalizedDescriptionKey: "Failed to load or convert image"] - ) + throwing: RuntimeError.error(withMessage: "Failed to load or convert image") ) } } From 91c1c78ff07338de4b414c7ef94f6fea1a3905e8 Mon Sep 17 00:00:00 2001 From: bglgwyng Date: Tue, 8 Jul 2025 17:11:50 +0900 Subject: [PATCH 05/24] refactor: rename `NitroMediaLibraryImageTab` to `NitroAssetImageTab` --- example/src/App.tsx | 4 ++-- .../{NitroMediaLibraryImageTab.tsx => NitroAssetImageTab.tsx} | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) rename example/src/{NitroMediaLibraryImageTab.tsx => NitroAssetImageTab.tsx} (93%) diff --git a/example/src/App.tsx b/example/src/App.tsx index 008f7ce0..dbd26313 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -11,7 +11,7 @@ import { FastImageTab } from "./FastImageTab"; import { NitroImageTab } from "./NitroImageTab"; import { createStaticNavigation } from "@react-navigation/native"; import { EmptyTab } from "./EmptyTab"; -import { NitroMediaLibraryImageTab } from "./NitroMediaLibraryImageTab"; +import { NitroAssetImageTab } from "./NitroAssetImageTab"; const Tabs = createBottomTabNavigator({ detachInactiveScreens: false, @@ -19,7 +19,7 @@ const Tabs = createBottomTabNavigator({ Empty: EmptyTab, FastImage: FastImageTab, NitroImage: NitroImageTab, - NitroMediaLibraryImage: NitroMediaLibraryImageTab, + NitroMediaLibraryImage: NitroAssetImageTab, }, }); const Navigation = createStaticNavigation(Tabs); diff --git a/example/src/NitroMediaLibraryImageTab.tsx b/example/src/NitroAssetImageTab.tsx similarity index 93% rename from example/src/NitroMediaLibraryImageTab.tsx rename to example/src/NitroAssetImageTab.tsx index f83b4bbd..857054d2 100644 --- a/example/src/NitroMediaLibraryImageTab.tsx +++ b/example/src/NitroAssetImageTab.tsx @@ -12,7 +12,7 @@ function useAssetImage( useEffect(() => { const load = async () => { try { - const i = await loadImageFromAssetAsync(url.replace("ph://", "")) + const i = await loadImageFromAssetAsync(url.replace(/^ph:\/\//, "")) setImage(i) } catch (error) { console.error(`Failed to load image from "${url}"!`, error) @@ -32,7 +32,7 @@ function AsyncImageImpl({ url }: { url: string }): React.ReactNode { } const AsyncImage = React.memo(AsyncImageImpl); -export function NitroMediaLibraryImageTab() { +export function NitroAssetImageTab() { const [imageURLs, setImageURLs] = useState([]); From e06fdaf735f1fef594b946842deff96b47fe75e5 Mon Sep 17 00:00:00 2001 From: bglgwyng Date: Tue, 8 Jul 2025 17:13:07 +0900 Subject: [PATCH 06/24] style: lint --- example/src/App.tsx | 14 +++++++------- example/src/NitroAssetImageTab.tsx | 24 ++++++++++++------------ 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/example/src/App.tsx b/example/src/App.tsx index dbd26313..2fbe930a 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -5,13 +5,13 @@ * @format */ -import type React from "react"; -import { createBottomTabNavigator } from "@react-navigation/bottom-tabs"; -import { FastImageTab } from "./FastImageTab"; -import { NitroImageTab } from "./NitroImageTab"; -import { createStaticNavigation } from "@react-navigation/native"; -import { EmptyTab } from "./EmptyTab"; -import { NitroAssetImageTab } from "./NitroAssetImageTab"; +import type React from 'react'; +import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; +import { FastImageTab } from './FastImageTab'; +import { NitroImageTab } from './NitroImageTab'; +import { createStaticNavigation } from '@react-navigation/native'; +import { EmptyTab } from './EmptyTab'; +import { NitroAssetImageTab } from './NitroAssetImageTab'; const Tabs = createBottomTabNavigator({ detachInactiveScreens: false, diff --git a/example/src/NitroAssetImageTab.tsx b/example/src/NitroAssetImageTab.tsx index 857054d2..a71638d0 100644 --- a/example/src/NitroAssetImageTab.tsx +++ b/example/src/NitroAssetImageTab.tsx @@ -1,30 +1,30 @@ import React, { useEffect, useState } from 'react'; import { FlatList, StyleSheet, Text, View } from 'react-native'; import { Image, loadImageFromAssetAsync, NitroImage } from 'react-native-nitro-image'; -import { CameraRoll } from "@react-native-camera-roll/camera-roll"; +import { CameraRoll } from '@react-native-camera-roll/camera-roll'; function useAssetImage( url: string, ): Image | undefined { - const [image, setImage] = useState(undefined) + const [image, setImage] = useState(undefined); useEffect(() => { const load = async () => { try { - const i = await loadImageFromAssetAsync(url.replace(/^ph:\/\//, "")) - setImage(i) + const i = await loadImageFromAssetAsync(url.replace(/^ph:\/\//, '')); + setImage(i); } catch (error) { - console.error(`Failed to load image from "${url}"!`, error) - setImage(undefined) + console.error(`Failed to load image from "${url}"!`, error); + setImage(undefined); } - } - load() + }; + load(); // `options` is missing from dependencies since it's a reference type that will be constructed each render. - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [url]) - return image + }, [url]); + + return image; } function AsyncImageImpl({ url }: { url: string }): React.ReactNode { const image = useAssetImage(url); @@ -37,7 +37,7 @@ export function NitroAssetImageTab() { useEffect(() => { - CameraRoll.getPhotos({ first: 100,assetType: "Photos" }).then((res) => { + CameraRoll.getPhotos({ first: 100,assetType: 'Photos' }).then((res) => { setImageURLs(res.edges.map((edge) => edge.node.image.uri)); }); }, []); From f9fd4ce6ab00c089be198e8ae0d60644dc3e0a89 Mon Sep 17 00:00:00 2001 From: bglgwyng Date: Wed, 9 Jul 2025 01:51:31 +0900 Subject: [PATCH 07/24] doc: update the `loadFromAssetAsync` description Co-authored-by: Marc Rousavy --- ios/HybridImageFactory.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ios/HybridImageFactory.swift b/ios/HybridImageFactory.swift index 245a86c3..7a89e6b8 100644 --- a/ios/HybridImageFactory.swift +++ b/ios/HybridImageFactory.swift @@ -31,7 +31,7 @@ class HybridImageFactory: HybridImageFactorySpec { } /** - * Load Image from URL + * Load Image from Photo Library Asset ID */ func loadFromAssetAsync(assetId: String, options: AssetImageLoadOptions?) throws -> Promise { let assets = PHAsset.fetchAssets( From 5f20be44081cbd33770e7943326f03b7b8a3fbfa Mon Sep 17 00:00:00 2001 From: bglgwyng Date: Wed, 9 Jul 2025 01:51:50 +0900 Subject: [PATCH 08/24] style: format Co-authored-by: Marc Rousavy --- ios/HybridImageFactory.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ios/HybridImageFactory.swift b/ios/HybridImageFactory.swift index 7a89e6b8..86ceef2d 100644 --- a/ios/HybridImageFactory.swift +++ b/ios/HybridImageFactory.swift @@ -105,7 +105,7 @@ class HybridImageFactory: HybridImageFactorySpec { case .right: uiImageOrientation = .right case .rightMirrored: uiImageOrientation = .rightMirrored } - let uiImage = UIImage(cgImage: cgImage, scale: 1, orientation:uiImageOrientation ) + let uiImage = UIImage(cgImage: cgImage, scale: 1, orientation: uiImageOrientation) continuation.resume(returning: HybridImage(uiImage: uiImage)) } else { continuation.resume( From efd82c3ccb4411f35739bfebbcaef2359df7d2d0 Mon Sep 17 00:00:00 2001 From: bglgwyng Date: Wed, 9 Jul 2025 01:54:38 +0900 Subject: [PATCH 09/24] style: format Co-authored-by: Marc Rousavy --- example/src/NitroAssetImageTab.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/example/src/NitroAssetImageTab.tsx b/example/src/NitroAssetImageTab.tsx index a71638d0..49d4b75d 100644 --- a/example/src/NitroAssetImageTab.tsx +++ b/example/src/NitroAssetImageTab.tsx @@ -35,7 +35,6 @@ const AsyncImage = React.memo(AsyncImageImpl); export function NitroAssetImageTab() { const [imageURLs, setImageURLs] = useState([]); - useEffect(() => { CameraRoll.getPhotos({ first: 100,assetType: 'Photos' }).then((res) => { setImageURLs(res.edges.map((edge) => edge.node.image.uri)); From fa2dd0bea4031b1526771fd8a70f1e85c1b37678 Mon Sep 17 00:00:00 2001 From: bglgwyng Date: Wed, 9 Jul 2025 01:59:37 +0900 Subject: [PATCH 10/24] fix: fix asset not found error message Co-authored-by: Marc Rousavy --- ios/HybridImageFactory.swift | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/ios/HybridImageFactory.swift b/ios/HybridImageFactory.swift index 86ceef2d..c01278ac 100644 --- a/ios/HybridImageFactory.swift +++ b/ios/HybridImageFactory.swift @@ -39,11 +39,7 @@ class HybridImageFactory: HybridImageFactorySpec { options: nil ) guard let asset = assets.firstObject else { - throw NSError( - domain: "TurboImageView", - code: 404, - userInfo: [NSLocalizedDescriptionKey: "Asset not found"] - ) + throw RuntimeError.error(withMessage: "Asset with ID \(assetId) was not found!") } return Promise.async { From a1c92f187d982119fce5ebfe316f886085222a58 Mon Sep 17 00:00:00 2001 From: bglgwyng Date: Wed, 9 Jul 2025 14:04:32 +0900 Subject: [PATCH 11/24] refactor: move orientation mapping to `CGImagePropertyOrientation` extension --- ios/HybridImageFactory.swift | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/ios/HybridImageFactory.swift b/ios/HybridImageFactory.swift index c01278ac..9482f6d1 100644 --- a/ios/HybridImageFactory.swift +++ b/ios/HybridImageFactory.swift @@ -89,19 +89,8 @@ class HybridImageFactory: HybridImageFactorySpec { if let error = info?[PHImageErrorKey] as? Error { continuation.resume(throwing: error) } else if let imageData = imageData, - let cgImage = UIImage(data:imageData)?.cgImage { - let uiImageOrientation: UIImage.Orientation - switch orientation { - case .up: uiImageOrientation = .up - case .upMirrored: uiImageOrientation = .upMirrored - case .down: uiImageOrientation = .down - case .downMirrored: uiImageOrientation = .downMirrored - case .left: uiImageOrientation = .left - case .leftMirrored: uiImageOrientation = .leftMirrored - case .right: uiImageOrientation = .right - case .rightMirrored: uiImageOrientation = .rightMirrored - } - let uiImage = UIImage(cgImage: cgImage, scale: 1, orientation: uiImageOrientation) + let cgImage = UIImage(data: imageData)?.cgImage { + let uiImage = UIImage(cgImage: cgImage, scale: 1, orientation: UIImage.Orientation(orientation)) continuation.resume(returning: HybridImage(uiImage: uiImage)) } else { continuation.resume( @@ -187,3 +176,18 @@ class HybridImageFactory: HybridImageFactorySpec { } } } + +extension UIImage.Orientation { + init(_ cgOrientation: CGImagePropertyOrientation) { + switch cgOrientation { + case .up: self = .up + case .upMirrored: self = .upMirrored + case .down: self = .down + case .downMirrored: self = .downMirrored + case .left: self = .left + case .leftMirrored: self = .leftMirrored + case .right: self = .right + case .rightMirrored: self = .rightMirrored + } + } +} From 3edca913b632ab33a293ebe0d2e8632342426aa1 Mon Sep 17 00:00:00 2001 From: bglgwyng Date: Wed, 9 Jul 2025 14:25:49 +0900 Subject: [PATCH 12/24] refactor: move `aspectFit` mapping to `PHImageContentMode` extension --- ios/HybridImageFactory.swift | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/ios/HybridImageFactory.swift b/ios/HybridImageFactory.swift index 9482f6d1..c1b0b88c 100644 --- a/ios/HybridImageFactory.swift +++ b/ios/HybridImageFactory.swift @@ -51,17 +51,7 @@ class HybridImageFactory: HybridImageFactorySpec { if let size = options?.size { - let contentMode: PHImageContentMode - if let aspectFit = options?.aspectFit { - switch aspectFit { - case .fit: - contentMode = .aspectFit - case .fill: - contentMode = .aspectFill - } - } else { - contentMode = .default - } + let contentMode = PHImageContentMode(options?.aspectFit) PHImageManager.default().requestImage( for: asset, @@ -191,3 +181,14 @@ extension UIImage.Orientation { } } } + +extension PHImageContentMode { + init(_ aspectFit: AspectFit?) { + switch aspectFit { + case .some(.fit), .none: + self = .aspectFit + case .some(.fill): + self = .aspectFill + } + } +} From e51f5632203796588dabc801e40b6c6b511e80d4 Mon Sep 17 00:00:00 2001 From: bglgwyng Date: Wed, 9 Jul 2025 14:39:54 +0900 Subject: [PATCH 13/24] refactor: separate definitions --- ios/HybridImageFactory.swift | 26 -------------------------- 1 file changed, 26 deletions(-) diff --git a/ios/HybridImageFactory.swift b/ios/HybridImageFactory.swift index c1b0b88c..154c3011 100644 --- a/ios/HybridImageFactory.swift +++ b/ios/HybridImageFactory.swift @@ -166,29 +166,3 @@ class HybridImageFactory: HybridImageFactorySpec { } } } - -extension UIImage.Orientation { - init(_ cgOrientation: CGImagePropertyOrientation) { - switch cgOrientation { - case .up: self = .up - case .upMirrored: self = .upMirrored - case .down: self = .down - case .downMirrored: self = .downMirrored - case .left: self = .left - case .leftMirrored: self = .leftMirrored - case .right: self = .right - case .rightMirrored: self = .rightMirrored - } - } -} - -extension PHImageContentMode { - init(_ aspectFit: AspectFit?) { - switch aspectFit { - case .some(.fit), .none: - self = .aspectFit - case .some(.fill): - self = .aspectFill - } - } -} From 900f07ac4f88d43853138dbbd560ac583f727449 Mon Sep 17 00:00:00 2001 From: bglgwyng Date: Wed, 9 Jul 2025 15:09:54 +0900 Subject: [PATCH 14/24] refactor: add async `PHImageManager` extension --- ios/HybridImageFactory.swift | 79 ++++++++---------------- ios/PHImageManager+Async.swift | 106 +++++++++++++++++++++++++++++++++ 2 files changed, 132 insertions(+), 53 deletions(-) create mode 100644 ios/PHImageManager+Async.swift diff --git a/ios/HybridImageFactory.swift b/ios/HybridImageFactory.swift index 154c3011..5e36ea69 100644 --- a/ios/HybridImageFactory.swift +++ b/ios/HybridImageFactory.swift @@ -34,61 +34,34 @@ class HybridImageFactory: HybridImageFactorySpec { * Load Image from Photo Library Asset ID */ func loadFromAssetAsync(assetId: String, options: AssetImageLoadOptions?) throws -> Promise { - let assets = PHAsset.fetchAssets( - withLocalIdentifiers: [assetId], - options: nil - ) - guard let asset = assets.firstObject else { - throw RuntimeError.error(withMessage: "Asset with ID \(assetId) was not found!") - } - return Promise.async { - return try await withCheckedThrowingContinuation { continuation in - let requestOptions = PHImageRequestOptions() - requestOptions.version = .current - requestOptions.deliveryMode = .highQualityFormat - requestOptions.isNetworkAccessAllowed = true - - - if let size = options?.size { - let contentMode = PHImageContentMode(options?.aspectFit) - - PHImageManager.default().requestImage( - for: asset, - targetSize: CGSize(width: size.width, height: size.height), - contentMode: contentMode, - options: requestOptions - ) { (image, info) in - if let error = info?[PHImageErrorKey] as? Error { - continuation.resume(throwing: error) - } else if let image = image { - continuation - .resume(returning: HybridImage(uiImage: image)) - } else { - continuation.resume( - throwing: RuntimeError.error(withMessage: "Failed to load or convert image") - ) - } - } - } else { - PHImageManager.default().requestImageDataAndOrientation( - for: asset, - options: requestOptions - ) { (imageData, dataUTI, orientation, info) in - - if let error = info?[PHImageErrorKey] as? Error { - continuation.resume(throwing: error) - } else if let imageData = imageData, - let cgImage = UIImage(data: imageData)?.cgImage { - let uiImage = UIImage(cgImage: cgImage, scale: 1, orientation: UIImage.Orientation(orientation)) - continuation.resume(returning: HybridImage(uiImage: uiImage)) - } else { - continuation.resume( - throwing: RuntimeError.error(withMessage: "Failed to load or convert image") - ) - } - } + // Move PHAsset fetching inside the async block + let asset = try await PHAsset.fetchAsset(withLocalIdentifier: assetId) + + let requestOptions = PHImageRequestOptions() + requestOptions.version = .current + requestOptions.deliveryMode = .highQualityFormat + requestOptions.isNetworkAccessAllowed = true + + if let size = options?.size { + let contentMode = PHImageContentMode(options?.aspectFit) + let uiImage = try await PHImageManager.default().requestImage( + for: asset, + targetSize: CGSize(width: size.width, height: size.height), + contentMode: contentMode, + options: requestOptions + ) + return HybridImage(uiImage: uiImage) + } else { + let (imageData, orientation) = try await PHImageManager.default().requestImageDataAndOrientation( + for: asset, + options: requestOptions + ) + guard let cgImage = UIImage(data: imageData)?.cgImage else { + throw RuntimeError.error(withMessage: "Failed to create CGImage from data") } + let uiImage = UIImage(cgImage: cgImage, scale: 1, orientation: UIImage.Orientation(orientation)) + return HybridImage(uiImage: uiImage) } } } diff --git a/ios/PHImageManager+Async.swift b/ios/PHImageManager+Async.swift new file mode 100644 index 00000000..619eb882 --- /dev/null +++ b/ios/PHImageManager+Async.swift @@ -0,0 +1,106 @@ +import Photos + +#if DEBUG +private func withThrowingContinuation(_ body: (CheckedContinuation) -> Void) async throws -> T { + return try await withCheckedThrowingContinuation(body) +} +#else +private func withThrowingContinuation(_ body: (CheckedContinuation) -> Void) async throws -> T { + return try await withUnsafeThrowingContinuation(body) +} +#endif + +@available(iOS 13.0, *) +extension PHImageManager { + /// Asynchronously requests an image for the specified asset. + /// - Parameters: + /// - asset: The asset whose image is requested. + /// - targetSize: The target size of the image. + /// - contentMode: The content mode for the image. + /// - options: Options for the image request. + /// - Returns: The requested image. + func requestImage( + for asset: PHAsset, + targetSize: CGSize, + contentMode: PHImageContentMode, + options: PHImageRequestOptions? + ) async throws -> UIImage { + return try await withThrowingContinuation { continuation in + requestImage( + for: asset, + targetSize: targetSize, + contentMode: contentMode, + options: options + ) { image, info in + if let error = info?[PHImageErrorKey] as? Error { + continuation.resume(throwing: error) + } else if let image = image { + continuation.resume(returning: image) + } else { + continuation.resume( + throwing: NSError( + domain: "PHImageManager", + code: -1, + userInfo: [NSLocalizedDescriptionKey: "Failed to load image"] + ) + ) + } + } + } + } + + /// Asynchronously requests image data and orientation for the specified asset. + /// - Parameters: + /// - asset: The asset whose image data is requested. + /// - options: Options for the image request. + /// - Returns: A tuple containing the image data and its orientation. + func requestImageDataAndOrientation( + for asset: PHAsset, + options: PHImageRequestOptions? + ) async throws -> (data: Data, orientation: CGImagePropertyOrientation) { + return try await withThrowingContinuation { continuation in + requestImageDataAndOrientation( + for: asset, + options: options + ) { data, _, orientation, info in + if let error = info?[PHImageErrorKey] as? Error { + continuation.resume(throwing: error) + } else if let data = data { + continuation.resume(returning: (data, orientation)) + } else { + continuation.resume( + throwing: NSError( + domain: "PHImageManager", + code: -1, + userInfo: [NSLocalizedDescriptionKey: "Failed to load image data"] + ) + ) + } + } + } + } +} + +// MARK: - PHAsset Extension + +extension PHAsset { + /// Asynchronously fetches a PHAsset by its local identifier. + /// - Parameter localIdentifier: The local identifier of the asset to fetch. + /// - Returns: The fetched asset if found. + static func fetchAsset(withLocalIdentifier localIdentifier: String) async throws -> PHAsset { + return try await withThrowingContinuation { continuation in + let fetchResult = PHAsset.fetchAssets(withLocalIdentifiers: [localIdentifier], options: nil) + if let asset = fetchResult.firstObject { + continuation.resume(returning: asset) + } else { + continuation.resume( + throwing: NSError( + domain: "PHAsset", + code: -1, + userInfo: [NSLocalizedDescriptionKey: "Asset with ID \(localIdentifier) was not found!"] + ) + ) + } + } + } +} From 5ea8d1fbde8d8d914b94a6822be80adb18333ac2 Mon Sep 17 00:00:00 2001 From: bglgwyng Date: Wed, 9 Jul 2025 19:16:56 +0900 Subject: [PATCH 15/24] feat: add unsupported `loadFromAssetAsync` method stub for Android platform --- .../main/java/com/margelo/nitro/image/HybridImageFactory.kt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/android/src/main/java/com/margelo/nitro/image/HybridImageFactory.kt b/android/src/main/java/com/margelo/nitro/image/HybridImageFactory.kt index 6895f9da..78cb7a19 100644 --- a/android/src/main/java/com/margelo/nitro/image/HybridImageFactory.kt +++ b/android/src/main/java/com/margelo/nitro/image/HybridImageFactory.kt @@ -55,6 +55,10 @@ class HybridImageFactory: HybridImageFactorySpec() { override fun loadFromResourcesAsync(name: String): Promise { return Promise.async { loadFromResources(name) } } + + override fun loadFromAssetAsync(assetName: String, options: AssetImageLoadOptions?): Promise { + throw Error("ImageFactory.loadFromAssetAsync(assetName:options:) is not supported on Android!") + } override fun loadFromSymbol(symbolName: String): HybridImageSpec { throw Error("ImageFactory.loadFromSymbol(symbolName:) is not supported on Android!") From 429bed1a9def0fb23b49fddc522b4597505de824 Mon Sep 17 00:00:00 2001 From: bglgwyng Date: Wed, 9 Jul 2025 19:36:59 +0900 Subject: [PATCH 16/24] doc: remove unnecessary comment --- example/src/NitroAssetImageTab.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/example/src/NitroAssetImageTab.tsx b/example/src/NitroAssetImageTab.tsx index 49d4b75d..5f110dc7 100644 --- a/example/src/NitroAssetImageTab.tsx +++ b/example/src/NitroAssetImageTab.tsx @@ -20,8 +20,6 @@ function useAssetImage( } }; load(); - // `options` is missing from dependencies since it's a reference type that will be constructed each render. - }, [url]); return image; From 526324862d9c38b78c37938a85cae96a65628ebd Mon Sep 17 00:00:00 2001 From: bglgwyng Date: Thu, 17 Jul 2025 01:06:26 +0900 Subject: [PATCH 17/24] chore: update lockfile --- example/ios/Podfile.lock | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index b039e290..61a9fc37 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -1420,7 +1420,7 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - Yoga - - react-native-safe-area-context (5.4.1): + - react-native-safe-area-context (5.5.2): - DoubleConversion - glog - hermes-engine @@ -1435,8 +1435,8 @@ PODS: - React-hermes - React-ImageManager - React-jsi - - react-native-safe-area-context/common (= 5.4.1) - - react-native-safe-area-context/fabric (= 5.4.1) + - react-native-safe-area-context/common (= 5.5.2) + - react-native-safe-area-context/fabric (= 5.5.2) - React-NativeModulesApple - React-RCTFabric - React-renderercss @@ -1446,7 +1446,7 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - Yoga - - react-native-safe-area-context/common (5.4.1): + - react-native-safe-area-context/common (5.5.2): - DoubleConversion - glog - hermes-engine @@ -1470,7 +1470,7 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - Yoga - - react-native-safe-area-context/fabric (5.4.1): + - react-native-safe-area-context/fabric (5.5.2): - DoubleConversion - glog - hermes-engine @@ -1821,7 +1821,7 @@ PODS: - React-Core - SDWebImage (~> 5.11.1) - SDWebImageWebPCoder (~> 0.8.4) - - RNScreens (4.11.1): + - RNScreens (4.12.0): - DoubleConversion - glog - hermes-engine @@ -1845,9 +1845,9 @@ PODS: - ReactCodegen - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - - RNScreens/common (= 4.11.1) + - RNScreens/common (= 4.12.0) - Yoga - - RNScreens/common (4.11.1): + - RNScreens/common (4.12.0): - DoubleConversion - glog - hermes-engine @@ -2167,7 +2167,7 @@ SPEC CHECKSUMS: React-Mapbuffer: fae8da2c01aeb7f26ad739731b6dba61fd02fd97 React-microtasksnativemodule: 20454ffccff553f0ee73fd20873aa8555a5867fb react-native-cameraroll: 41084e42ab4ec08940452737aca3fd5e0edc63fe - react-native-safe-area-context: 5594ec631ede9c311c5c0efa244228eff845ce88 + react-native-safe-area-context: 7e926a200d4bc9c56562275743705c6b56176455 React-NativeModulesApple: 65b2735133d6ce8a3cb5f23215ef85e427b0139c React-oscompat: f26aa2a4adc84c34212ab12c07988fe19e9cf16a React-perflogger: e15a0d43d1928e1c82f4f0b7fc05f7e9bccfede8 @@ -2200,7 +2200,7 @@ SPEC CHECKSUMS: ReactCodegen: 16c2bfcebf870208d7e29ff0c065f4c0fa03034d ReactCommon: e243aa261effc83c10208f0794bade55ca9ae5b6 RNFastImage: 462a183c4b0b6b26fdfd639e1ed6ba37536c3b87 - RNScreens: 482e9707f9826230810c92e765751af53826d509 + RNScreens: d8f03344886bd566bba24b9e02dbd28979631d3e SDWebImage: a7f831e1a65eb5e285e3fb046a23fcfbf08e696d SDWebImageWebPCoder: 908b83b6adda48effe7667cd2b7f78c897e5111d SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748 From fb8984790ca48edfa708fcba797c67260220d33a Mon Sep 17 00:00:00 2001 From: bglgwyng Date: Thu, 17 Jul 2025 01:44:04 +0900 Subject: [PATCH 18/24] fix: add missing files --- ios/HybridImageFactory.swift | 4 ++-- ios/PHImageContentMode+AspectFit.swift | 18 +++++++++++++++ ...ientation+CGImagePropertyOrientation.swift | 22 +++++++++++++++++++ 3 files changed, 42 insertions(+), 2 deletions(-) create mode 100644 ios/PHImageContentMode+AspectFit.swift create mode 100644 ios/UIImageOrientation+CGImagePropertyOrientation.swift diff --git a/ios/HybridImageFactory.swift b/ios/HybridImageFactory.swift index 5e36ea69..a1b29cb8 100644 --- a/ios/HybridImageFactory.swift +++ b/ios/HybridImageFactory.swift @@ -44,7 +44,7 @@ class HybridImageFactory: HybridImageFactorySpec { requestOptions.isNetworkAccessAllowed = true if let size = options?.size { - let contentMode = PHImageContentMode(options?.aspectFit) + let contentMode = PHImageContentMode(aspectFit: options?.aspectFit) let uiImage = try await PHImageManager.default().requestImage( for: asset, targetSize: CGSize(width: size.width, height: size.height), @@ -60,7 +60,7 @@ class HybridImageFactory: HybridImageFactorySpec { guard let cgImage = UIImage(data: imageData)?.cgImage else { throw RuntimeError.error(withMessage: "Failed to create CGImage from data") } - let uiImage = UIImage(cgImage: cgImage, scale: 1, orientation: UIImage.Orientation(orientation)) + let uiImage = UIImage(cgImage: cgImage, scale: 1, orientation: UIImage.Orientation(cgImageOrientation: orientation)) return HybridImage(uiImage: uiImage) } } diff --git a/ios/PHImageContentMode+AspectFit.swift b/ios/PHImageContentMode+AspectFit.swift new file mode 100644 index 00000000..b156b333 --- /dev/null +++ b/ios/PHImageContentMode+AspectFit.swift @@ -0,0 +1,18 @@ +// +// PHImageContentMode+AspectFit.swift +// Pods +// +// Created by bgl gwyng on 7/17/25. +// + +import Photos + +extension PHImageContentMode { + init(aspectFit: AspectFit?) { + switch aspectFit { + case .fill: self = .aspectFill + case .fit: self = .aspectFit + default: self = .default + } + } +} diff --git a/ios/UIImageOrientation+CGImagePropertyOrientation.swift b/ios/UIImageOrientation+CGImagePropertyOrientation.swift new file mode 100644 index 00000000..d9922678 --- /dev/null +++ b/ios/UIImageOrientation+CGImagePropertyOrientation.swift @@ -0,0 +1,22 @@ +// +// UIImageOrientation+dc.swift +// Pods +// +// Created by bgl gwyng on 7/17/25. +// + +extension UIImage.Orientation { + init(cgImageOrientation: CGImagePropertyOrientation) { + switch cgImageOrientation { + case .up: self = .up + case .upMirrored: self = .upMirrored + case .down: self = .down + case .downMirrored: self = .downMirrored + case .left: self = .left + case .leftMirrored: self = .leftMirrored + case .right: self = .right + case .rightMirrored: self = .rightMirrored + @unknown default: self = .up + } + } +} From 0ebe155da0b63b7b6233c8b766ddb3817900dc6f Mon Sep 17 00:00:00 2001 From: bglgwyng Date: Thu, 17 Jul 2025 01:47:03 +0900 Subject: [PATCH 19/24] chore: replace eslint with biome for linting --- example/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/example/package.json b/example/package.json index 7c674a10..2619d888 100644 --- a/example/package.json +++ b/example/package.json @@ -5,7 +5,7 @@ "scripts": { "android": "react-native run-android", "ios": "react-native run-ios", - "lint": "eslint .", + "lint": "biome check . --fix", "start": "react-native start --client-logs", "pods": "bundle install && cd ios && bundle exec pod install" }, @@ -37,4 +37,4 @@ "engines": { "node": ">=18" } -} +} \ No newline at end of file From 19db2010f704a2b4b9482acbd1d78086f596a778 Mon Sep 17 00:00:00 2001 From: bglgwyng Date: Thu, 17 Jul 2025 01:53:30 +0900 Subject: [PATCH 20/24] style: lint --- example/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example/package.json b/example/package.json index 2619d888..517f81fc 100644 --- a/example/package.json +++ b/example/package.json @@ -37,4 +37,4 @@ "engines": { "node": ">=18" } -} \ No newline at end of file +} From 14e50a8df089c61e1d3c9491dcf5a416360c85f5 Mon Sep 17 00:00:00 2001 From: bglgwyng Date: Thu, 17 Jul 2025 15:42:07 +0900 Subject: [PATCH 21/24] feat: add privacy access reason 3B52.1 to PrivacyInfo.xcprivacy --- example/ios/NitroImageExample/PrivacyInfo.xcprivacy | 1 + 1 file changed, 1 insertion(+) diff --git a/example/ios/NitroImageExample/PrivacyInfo.xcprivacy b/example/ios/NitroImageExample/PrivacyInfo.xcprivacy index 41b8317f..5b037f0c 100644 --- a/example/ios/NitroImageExample/PrivacyInfo.xcprivacy +++ b/example/ios/NitroImageExample/PrivacyInfo.xcprivacy @@ -10,6 +10,7 @@ NSPrivacyAccessedAPITypeReasons C617.1 + 3B52.1 From 76cefdf6284c9095a49e5264fb0d809cdc035c1e Mon Sep 17 00:00:00 2001 From: bglgwyng Date: Sat, 19 Jul 2025 20:13:44 +0900 Subject: [PATCH 22/24] ci: set Xcode 16.4 version for iOS build workflow --- .github/workflows/build-ios.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/build-ios.yml b/.github/workflows/build-ios.yml index e05f867f..c6998443 100644 --- a/.github/workflows/build-ios.yml +++ b/.github/workflows/build-ios.yml @@ -39,6 +39,9 @@ jobs: - uses: actions/checkout@v4 - uses: oven-sh/setup-bun@v2 + - name: Select Xcode version + run: sudo xcode-select -s /Applications/Xcode_16.4.app/Contents/Developer + - name: Install npm dependencies (bun) run: bun install From 2864e3b260c27d2387074894a23b2f5a67e1786f Mon Sep 17 00:00:00 2001 From: bglgwyng Date: Thu, 24 Jul 2025 10:44:29 +0900 Subject: [PATCH 23/24] chore: style --- example/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example/package.json b/example/package.json index 0e9d68cf..f8fbb32f 100644 --- a/example/package.json +++ b/example/package.json @@ -37,4 +37,4 @@ "engines": { "node": ">=18" } -} \ No newline at end of file +} From ffa92b8d9fac79c7bdbdfd14d13df1b978bce4bc Mon Sep 17 00:00:00 2001 From: bglgwyng Date: Thu, 24 Jul 2025 10:50:07 +0900 Subject: [PATCH 24/24] fix: rerun nitrogen --- .../ios/NitroImage-Swift-Cxx-Bridge.hpp | 30 +++++++++---------- .../ios/NitroImage-Swift-Cxx-Umbrella.hpp | 2 +- ...nc_void_std__shared_ptr_ArrayBuffer_.swift | 8 ++--- .../ios/swift/HybridImageFactorySpec.swift | 8 ++--- .../swift/HybridImageFactorySpec_cxx.swift | 8 ++--- .../generated/ios/swift/HybridImageSpec.swift | 8 ++--- .../ios/swift/HybridImageUtilsSpec.swift | 4 +-- .../ios/swift/HybridImageUtilsSpec_cxx.swift | 2 +- 8 files changed, 35 insertions(+), 35 deletions(-) diff --git a/nitrogen/generated/ios/NitroImage-Swift-Cxx-Bridge.hpp b/nitrogen/generated/ios/NitroImage-Swift-Cxx-Bridge.hpp index 38e274bc..fd9e0b39 100644 --- a/nitrogen/generated/ios/NitroImage-Swift-Cxx-Bridge.hpp +++ b/nitrogen/generated/ios/NitroImage-Swift-Cxx-Bridge.hpp @@ -93,13 +93,13 @@ namespace margelo::nitro::image::bridge::swift { */ class Func_void_std__shared_ptr_ArrayBuffer__Wrapper final { public: - explicit Func_void_std__shared_ptr_ArrayBuffer__Wrapper(std::function& /* result */)>&& func): _function(std::make_unique& /* result */)>>(std::move(func))) {} + explicit Func_void_std__shared_ptr_ArrayBuffer__Wrapper(std::function& /* result */)>&& func): _function(std::make_shared& /* result */)>>(std::move(func))) {} inline void call(ArrayBufferHolder result) const { _function->operator()(result.getArrayBuffer()); } private: - std::unique_ptr& /* result */)>> _function; - } SWIFT_NONCOPYABLE; + std::shared_ptr& /* result */)>> _function; + }; Func_void_std__shared_ptr_ArrayBuffer_ create_Func_void_std__shared_ptr_ArrayBuffer_(void* _Nonnull swiftClosureWrapper); inline Func_void_std__shared_ptr_ArrayBuffer__Wrapper wrap_Func_void_std__shared_ptr_ArrayBuffer_(Func_void_std__shared_ptr_ArrayBuffer_ value) { return Func_void_std__shared_ptr_ArrayBuffer__Wrapper(std::move(value)); @@ -115,13 +115,13 @@ namespace margelo::nitro::image::bridge::swift { */ class Func_void_std__exception_ptr_Wrapper final { public: - explicit Func_void_std__exception_ptr_Wrapper(std::function&& func): _function(std::make_unique>(std::move(func))) {} + explicit Func_void_std__exception_ptr_Wrapper(std::function&& func): _function(std::make_shared>(std::move(func))) {} inline void call(std::exception_ptr error) const { _function->operator()(error); } private: - std::unique_ptr> _function; - } SWIFT_NONCOPYABLE; + std::shared_ptr> _function; + }; Func_void_std__exception_ptr create_Func_void_std__exception_ptr(void* _Nonnull swiftClosureWrapper); inline Func_void_std__exception_ptr_Wrapper wrap_Func_void_std__exception_ptr(Func_void_std__exception_ptr value) { return Func_void_std__exception_ptr_Wrapper(std::move(value)); @@ -161,13 +161,13 @@ namespace margelo::nitro::image::bridge::swift { */ class Func_void_std__shared_ptr_margelo__nitro__image__HybridImageSpec__Wrapper final { public: - explicit Func_void_std__shared_ptr_margelo__nitro__image__HybridImageSpec__Wrapper(std::function& /* result */)>&& func): _function(std::make_unique& /* result */)>>(std::move(func))) {} + explicit Func_void_std__shared_ptr_margelo__nitro__image__HybridImageSpec__Wrapper(std::function& /* result */)>&& func): _function(std::make_shared& /* result */)>>(std::move(func))) {} inline void call(std::shared_ptr result) const { _function->operator()(result); } private: - std::unique_ptr& /* result */)>> _function; - } SWIFT_NONCOPYABLE; + std::shared_ptr& /* result */)>> _function; + }; Func_void_std__shared_ptr_margelo__nitro__image__HybridImageSpec_ create_Func_void_std__shared_ptr_margelo__nitro__image__HybridImageSpec_(void* _Nonnull swiftClosureWrapper); inline Func_void_std__shared_ptr_margelo__nitro__image__HybridImageSpec__Wrapper wrap_Func_void_std__shared_ptr_margelo__nitro__image__HybridImageSpec_(Func_void_std__shared_ptr_margelo__nitro__image__HybridImageSpec_ value) { return Func_void_std__shared_ptr_margelo__nitro__image__HybridImageSpec__Wrapper(std::move(value)); @@ -195,13 +195,13 @@ namespace margelo::nitro::image::bridge::swift { */ class Func_void_Wrapper final { public: - explicit Func_void_Wrapper(std::function&& func): _function(std::make_unique>(std::move(func))) {} + explicit Func_void_Wrapper(std::function&& func): _function(std::make_shared>(std::move(func))) {} inline void call() const { _function->operator()(); } private: - std::unique_ptr> _function; - } SWIFT_NONCOPYABLE; + std::shared_ptr> _function; + }; Func_void create_Func_void(void* _Nonnull swiftClosureWrapper); inline Func_void_Wrapper wrap_Func_void(Func_void value) { return Func_void_Wrapper(std::move(value)); @@ -229,13 +229,13 @@ namespace margelo::nitro::image::bridge::swift { */ class Func_void_std__string_Wrapper final { public: - explicit Func_void_std__string_Wrapper(std::function&& func): _function(std::make_unique>(std::move(func))) {} + explicit Func_void_std__string_Wrapper(std::function&& func): _function(std::make_shared>(std::move(func))) {} inline void call(std::string result) const { _function->operator()(result); } private: - std::unique_ptr> _function; - } SWIFT_NONCOPYABLE; + std::shared_ptr> _function; + }; Func_void_std__string create_Func_void_std__string(void* _Nonnull swiftClosureWrapper); inline Func_void_std__string_Wrapper wrap_Func_void_std__string(Func_void_std__string value) { return Func_void_std__string_Wrapper(std::move(value)); diff --git a/nitrogen/generated/ios/NitroImage-Swift-Cxx-Umbrella.hpp b/nitrogen/generated/ios/NitroImage-Swift-Cxx-Umbrella.hpp index e1aea3a3..d1055892 100644 --- a/nitrogen/generated/ios/NitroImage-Swift-Cxx-Umbrella.hpp +++ b/nitrogen/generated/ios/NitroImage-Swift-Cxx-Umbrella.hpp @@ -58,7 +58,7 @@ namespace margelo::nitro::image { enum class ResizeMode; } // Common C++ types used in Swift #include -#include +#include #include #include diff --git a/nitrogen/generated/ios/swift/Func_void_std__shared_ptr_ArrayBuffer_.swift b/nitrogen/generated/ios/swift/Func_void_std__shared_ptr_ArrayBuffer_.swift index 0489f3a4..0589d9e2 100644 --- a/nitrogen/generated/ios/swift/Func_void_std__shared_ptr_ArrayBuffer_.swift +++ b/nitrogen/generated/ios/swift/Func_void_std__shared_ptr_ArrayBuffer_.swift @@ -8,20 +8,20 @@ import NitroModules /** - * Wraps a Swift `(_ value: ArrayBuffer) -> Void` as a class. + * Wraps a Swift `(_ value: ArrayBufferHolder) -> Void` as a class. * This class can be used from C++, e.g. to wrap the Swift closure as a `std::function`. */ public final class Func_void_std__shared_ptr_ArrayBuffer_ { public typealias bridge = margelo.nitro.image.bridge.swift - private let closure: (_ value: ArrayBuffer) -> Void + private let closure: (_ value: ArrayBufferHolder) -> Void - public init(_ closure: @escaping (_ value: ArrayBuffer) -> Void) { + public init(_ closure: @escaping (_ value: ArrayBufferHolder) -> Void) { self.closure = closure } @inline(__always) - public func call(value: ArrayBuffer) -> Void { + public func call(value: ArrayBufferHolder) -> Void { self.closure(value) } diff --git a/nitrogen/generated/ios/swift/HybridImageFactorySpec.swift b/nitrogen/generated/ios/swift/HybridImageFactorySpec.swift index f5e0f3f3..bba2466d 100644 --- a/nitrogen/generated/ios/swift/HybridImageFactorySpec.swift +++ b/nitrogen/generated/ios/swift/HybridImageFactorySpec.swift @@ -21,10 +21,10 @@ public protocol HybridImageFactorySpec_protocol: HybridObject { func loadFromResources(name: String) throws -> (any HybridImageSpec) func loadFromResourcesAsync(name: String) throws -> Promise<(any HybridImageSpec)> func loadFromSymbol(symbolName: String) throws -> (any HybridImageSpec) - func loadFromArrayBuffer(buffer: ArrayBuffer) throws -> (any HybridImageSpec) - func loadFromArrayBufferAsync(buffer: ArrayBuffer) throws -> Promise<(any HybridImageSpec)> - func loadFromThumbHash(thumbhash: ArrayBuffer) throws -> (any HybridImageSpec) - func loadFromThumbHashAsync(thumbhash: ArrayBuffer) throws -> Promise<(any HybridImageSpec)> + func loadFromArrayBuffer(buffer: ArrayBufferHolder) throws -> (any HybridImageSpec) + func loadFromArrayBufferAsync(buffer: ArrayBufferHolder) throws -> Promise<(any HybridImageSpec)> + func loadFromThumbHash(thumbhash: ArrayBufferHolder) throws -> (any HybridImageSpec) + func loadFromThumbHashAsync(thumbhash: ArrayBufferHolder) throws -> Promise<(any HybridImageSpec)> } /// See ``HybridImageFactorySpec`` diff --git a/nitrogen/generated/ios/swift/HybridImageFactorySpec_cxx.swift b/nitrogen/generated/ios/swift/HybridImageFactorySpec_cxx.swift index 213139ae..79c1c32d 100644 --- a/nitrogen/generated/ios/swift/HybridImageFactorySpec_cxx.swift +++ b/nitrogen/generated/ios/swift/HybridImageFactorySpec_cxx.swift @@ -246,7 +246,7 @@ public class HybridImageFactorySpec_cxx { } @inline(__always) - public final func loadFromArrayBuffer(buffer: ArrayBuffer) -> bridge.Result_std__shared_ptr_margelo__nitro__image__HybridImageSpec__ { + public final func loadFromArrayBuffer(buffer: ArrayBufferHolder) -> bridge.Result_std__shared_ptr_margelo__nitro__image__HybridImageSpec__ { do { let __result = try self.__implementation.loadFromArrayBuffer(buffer: buffer) let __resultCpp = { () -> bridge.std__shared_ptr_margelo__nitro__image__HybridImageSpec_ in @@ -261,7 +261,7 @@ public class HybridImageFactorySpec_cxx { } @inline(__always) - public final func loadFromArrayBufferAsync(buffer: ArrayBuffer) -> bridge.Result_std__shared_ptr_Promise_std__shared_ptr_margelo__nitro__image__HybridImageSpec____ { + public final func loadFromArrayBufferAsync(buffer: ArrayBufferHolder) -> bridge.Result_std__shared_ptr_Promise_std__shared_ptr_margelo__nitro__image__HybridImageSpec____ { do { let __result = try self.__implementation.loadFromArrayBufferAsync(buffer: buffer) let __resultCpp = { () -> bridge.std__shared_ptr_Promise_std__shared_ptr_margelo__nitro__image__HybridImageSpec___ in @@ -283,7 +283,7 @@ public class HybridImageFactorySpec_cxx { } @inline(__always) - public final func loadFromThumbHash(thumbhash: ArrayBuffer) -> bridge.Result_std__shared_ptr_margelo__nitro__image__HybridImageSpec__ { + public final func loadFromThumbHash(thumbhash: ArrayBufferHolder) -> bridge.Result_std__shared_ptr_margelo__nitro__image__HybridImageSpec__ { do { let __result = try self.__implementation.loadFromThumbHash(thumbhash: thumbhash) let __resultCpp = { () -> bridge.std__shared_ptr_margelo__nitro__image__HybridImageSpec_ in @@ -298,7 +298,7 @@ public class HybridImageFactorySpec_cxx { } @inline(__always) - public final func loadFromThumbHashAsync(thumbhash: ArrayBuffer) -> bridge.Result_std__shared_ptr_Promise_std__shared_ptr_margelo__nitro__image__HybridImageSpec____ { + public final func loadFromThumbHashAsync(thumbhash: ArrayBufferHolder) -> bridge.Result_std__shared_ptr_Promise_std__shared_ptr_margelo__nitro__image__HybridImageSpec____ { do { let __result = try self.__implementation.loadFromThumbHashAsync(thumbhash: thumbhash) let __resultCpp = { () -> bridge.std__shared_ptr_Promise_std__shared_ptr_margelo__nitro__image__HybridImageSpec___ in diff --git a/nitrogen/generated/ios/swift/HybridImageSpec.swift b/nitrogen/generated/ios/swift/HybridImageSpec.swift index 87a4478e..b86d277c 100644 --- a/nitrogen/generated/ios/swift/HybridImageSpec.swift +++ b/nitrogen/generated/ios/swift/HybridImageSpec.swift @@ -15,16 +15,16 @@ public protocol HybridImageSpec_protocol: HybridObject { var height: Double { get } // Methods - func toArrayBuffer() throws -> ArrayBuffer - func toArrayBufferAsync() throws -> Promise + func toArrayBuffer() throws -> ArrayBufferHolder + func toArrayBufferAsync() throws -> Promise func resize(width: Double, height: Double) throws -> (any HybridImageSpec) func resizeAsync(width: Double, height: Double) throws -> Promise<(any HybridImageSpec)> func crop(startX: Double, startY: Double, endX: Double, endY: Double) throws -> (any HybridImageSpec) func cropAsync(startX: Double, startY: Double, endX: Double, endY: Double) throws -> Promise<(any HybridImageSpec)> func saveToFileAsync(path: String, format: ImageFormat, quality: Double) throws -> Promise func saveToTemporaryFileAsync(format: ImageFormat, quality: Double) throws -> Promise - func toThumbHash() throws -> ArrayBuffer - func toThumbHashAsync() throws -> Promise + func toThumbHash() throws -> ArrayBufferHolder + func toThumbHashAsync() throws -> Promise } /// See ``HybridImageSpec`` diff --git a/nitrogen/generated/ios/swift/HybridImageUtilsSpec.swift b/nitrogen/generated/ios/swift/HybridImageUtilsSpec.swift index d6511093..215f506f 100644 --- a/nitrogen/generated/ios/swift/HybridImageUtilsSpec.swift +++ b/nitrogen/generated/ios/swift/HybridImageUtilsSpec.swift @@ -14,8 +14,8 @@ public protocol HybridImageUtilsSpec_protocol: HybridObject { // Methods - func thumbHashToBase64String(thumbhash: ArrayBuffer) throws -> String - func thumbhashFromBase64String(thumbhashBase64: String) throws -> ArrayBuffer + func thumbHashToBase64String(thumbhash: ArrayBufferHolder) throws -> String + func thumbhashFromBase64String(thumbhashBase64: String) throws -> ArrayBufferHolder } /// See ``HybridImageUtilsSpec`` diff --git a/nitrogen/generated/ios/swift/HybridImageUtilsSpec_cxx.swift b/nitrogen/generated/ios/swift/HybridImageUtilsSpec_cxx.swift index a62d20d3..8dcdb96d 100644 --- a/nitrogen/generated/ios/swift/HybridImageUtilsSpec_cxx.swift +++ b/nitrogen/generated/ios/swift/HybridImageUtilsSpec_cxx.swift @@ -101,7 +101,7 @@ public class HybridImageUtilsSpec_cxx { // Methods @inline(__always) - public final func thumbHashToBase64String(thumbhash: ArrayBuffer) -> bridge.Result_std__string_ { + public final func thumbHashToBase64String(thumbhash: ArrayBufferHolder) -> bridge.Result_std__string_ { do { let __result = try self.__implementation.thumbHashToBase64String(thumbhash: thumbhash) let __resultCpp = std.string(__result)