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 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!") diff --git a/bun.lock b/bun.lock index e50b21d6..818e1529 100644 --- a/bun.lock +++ b/bun.lock @@ -24,6 +24,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", @@ -382,6 +383,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=="], @@ -742,7 +745,7 @@ "ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="], - "electron-to-chromium": ["electron-to-chromium@1.5.183", "", {}, "sha512-vCrDBYjQCAEefWGjlK3EpoSKfKbT10pR4XXPdn65q7snuNOZnthoVpBfZPykmDapOKfoD+MMIPG8ZjKyyc9oHA=="], + "electron-to-chromium": ["electron-to-chromium@1.5.185", "", {}, "sha512-dYOZfUk57hSMPePoIQ1fZWl1Fkj+OshhEVuPacNKWzC1efe56OsHY3l/jCfiAgIICOU3VgOIdoq7ahg7r7n6MQ=="], "emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], @@ -1240,7 +1243,7 @@ "react-native-safe-area-context": ["react-native-safe-area-context@5.5.2", "", { "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-t4YVbHa9uAGf+pHMabGrb0uHrD5ogAusSu842oikJ3YKXcYp6iB4PTGl0EZNkUIR3pCnw/CXKn42OCfhsS0JIw=="], - "react-native-screens": ["react-native-screens@4.11.1", "", { "dependencies": { "react-freeze": "^1.0.0", "react-native-is-edge-to-edge": "^1.1.7", "warn-once": "^0.1.0" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-F0zOzRVa3ptZfLpD0J8ROdo+y1fEPw+VBFq1MTY/iyDu08al7qFUO5hLMd+EYMda5VXGaTFCa8q7bOppUszhJw=="], + "react-native-screens": ["react-native-screens@4.12.0", "", { "dependencies": { "react-freeze": "^1.0.0", "react-native-is-edge-to-edge": "^1.1.7", "warn-once": "^0.1.0" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-T2KL6RcDSYDRZswh9glRe600Hvaeq240U21eaqv0uxCNmJz05UeFc4YGQgbFPI8XsakPKx3HjNonItxElFy+QA=="], "react-refresh": ["react-refresh@0.14.2", "", {}, "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA=="], 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/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 diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index 54fc13f7..6b18f0cf 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -47,7 +47,7 @@ PODS: - ReactCommon/turbomodule/core - SDWebImage - Yoga - - NitroModules (0.26.3): + - NitroModules (0.26.4): - DoubleConversion - glog - hermes-engine @@ -1396,7 +1396,7 @@ PODS: - React-jsiexecutor - React-RCTFBReactNativeSpec - ReactCommon/turbomodule/core - - react-native-safe-area-context (5.4.1): + - react-native-cameraroll (7.10.1): - DoubleConversion - glog - hermes-engine @@ -1411,8 +1411,6 @@ 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-NativeModulesApple - React-RCTFabric - React-renderercss @@ -1422,7 +1420,7 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - Yoga - - react-native-safe-area-context/common (5.4.1): + - react-native-safe-area-context (5.5.2): - DoubleConversion - glog - hermes-engine @@ -1437,6 +1435,8 @@ PODS: - React-hermes - React-ImageManager - React-jsi + - 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,31 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - Yoga - - react-native-safe-area-context/fabric (5.4.1): + - react-native-safe-area-context/common (5.5.2): + - 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/fabric (5.5.2): - DoubleConversion - glog - hermes-engine @@ -1797,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 @@ -1821,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 @@ -1866,7 +1890,7 @@ DEPENDENCIES: - glog (from `../../node_modules/react-native/third-party-podspecs/glog.podspec`) - hermes-engine (from `../../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec`) - NitroImage (from `../node_modules/react-native-nitro-image`) - - NitroModules (from `../../node_modules/react-native-nitro-modules`) + - NitroModules (from `../node_modules/react-native-nitro-modules`) - RCT-Folly (from `../../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec`) - RCT-Folly/Fabric (from `../../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec`) - RCTDeprecation (from `../../node_modules/react-native/ReactApple/Libraries/RCTFoundation/RCTDeprecation`) @@ -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`) @@ -1963,7 +1988,7 @@ EXTERNAL SOURCES: NitroImage: :path: "../node_modules/react-native-nitro-image" NitroModules: - :path: "../../node_modules/react-native-nitro-modules" + :path: "../node_modules/react-native-nitro-modules" RCT-Folly: :podspec: "../../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec" RCTDeprecation: @@ -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: @@ -2107,7 +2134,7 @@ SPEC CHECKSUMS: hermes-engine: 94ed01537bdeccaab1adbf94b040d115d6fa1a7f libwebp: 02b23773aedb6ff1fd38cec7a77b81414c6842a8 NitroImage: 7f0a8fda3268c7169c9f4f23437d5841cbf4c96a - NitroModules: f36b94e48ff1705fc6b84bc1953f40e2da4196c2 + NitroModules: 763fe03c46a734a615e648ff2c77158cd26d8f89 RCT-Folly: e78785aa9ba2ed998ea4151e314036f6c49e6d82 RCTDeprecation: c3e3f5b4ea83e7ff3bc86ce09e2a54b7affd687d RCTRequired: ee438439880dffc9425930d1dd1a3c883ee6879c @@ -2139,7 +2166,8 @@ SPEC CHECKSUMS: React-logger: 514fac028fee60c84591f951c7c04ba1c5023334 React-Mapbuffer: fae8da2c01aeb7f26ad739731b6dba61fd02fd97 React-microtasksnativemodule: 20454ffccff553f0ee73fd20873aa8555a5867fb - react-native-safe-area-context: 5594ec631ede9c311c5c0efa244228eff845ce88 + react-native-cameraroll: 41084e42ab4ec08940452737aca3fd5e0edc63fe + react-native-safe-area-context: 7e926a200d4bc9c56562275743705c6b56176455 React-NativeModulesApple: 65b2735133d6ce8a3cb5f23215ef85e427b0139c React-oscompat: f26aa2a4adc84c34212ab12c07988fe19e9cf16a React-perflogger: e15a0d43d1928e1c82f4f0b7fc05f7e9bccfede8 @@ -2172,7 +2200,7 @@ SPEC CHECKSUMS: ReactCodegen: 16c2bfcebf870208d7e29ff0c065f4c0fa03034d ReactCommon: e243aa261effc83c10208f0794bade55ca9ae5b6 RNFastImage: 462a183c4b0b6b26fdfd639e1ed6ba37536c3b87 - RNScreens: 482e9707f9826230810c92e765751af53826d509 + RNScreens: d8f03344886bd566bba24b9e02dbd28979631d3e SDWebImage: a7f831e1a65eb5e285e3fb046a23fcfbf08e696d SDWebImageWebPCoder: 908b83b6adda48effe7667cd2b7f78c897e5111d SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748 diff --git a/example/package.json b/example/package.json index 3bfa5c96..f8fbb32f 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 3c25ec61..59dac2b4 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -9,6 +9,7 @@ import { createBottomTabNavigator } from "@react-navigation/bottom-tabs"; import { createStaticNavigation } from "@react-navigation/native"; import { EmptyTab } from "./EmptyTab"; import { FastImageTab } from "./FastImageTab"; +import { NitroAssetImageTab } from "./NitroAssetImageTab"; import { NitroImageTab } from "./NitroImageTab"; const Tabs = createBottomTabNavigator({ @@ -17,6 +18,7 @@ const Tabs = createBottomTabNavigator({ Empty: EmptyTab, FastImage: FastImageTab, NitroImage: NitroImageTab, + NitroMediaLibraryImage: NitroAssetImageTab, }, }); const Navigation = createStaticNavigation(Tabs); diff --git a/example/src/NitroAssetImageTab.tsx b/example/src/NitroAssetImageTab.tsx new file mode 100644 index 00000000..aca0554b --- /dev/null +++ b/example/src/NitroAssetImageTab.tsx @@ -0,0 +1,65 @@ +import { CameraRoll } from "@react-native-camera-roll/camera-roll"; +import React, { useEffect, useState } from "react"; +import { FlatList, StyleSheet, Text, View } from "react-native"; +import { + type Image, + loadImageFromAssetAsync, + NitroImage, +} from "react-native-nitro-image"; + +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(); + }, [url]); + + return image; +} +function AsyncImageImpl({ url }: { url: string }): React.ReactNode { + const image = useAssetImage(url); + return ; +} +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)); + }, + ); + }, []); + + return ( + + NitroMediaLibraryImage Tab + } + /> + + ); +} + +const styles = StyleSheet.create({ + image: { + width: "25%", + aspectRatio: 1, + }, +}); diff --git a/ios/HybridImageFactory.swift b/ios/HybridImageFactory.swift index e5e75646..a1b29cb8 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", @@ -21,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) @@ -29,6 +30,42 @@ class HybridImageFactory: HybridImageFactorySpec { } } + /** + * Load Image from Photo Library Asset ID + */ + func loadFromAssetAsync(assetId: String, options: AssetImageLoadOptions?) throws -> Promise { + return Promise.async { + // 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(aspectFit: 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(cgImageOrientation: orientation)) + return HybridImage(uiImage: uiImage) + } + } + } + /** * Load Image from file path */ 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/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!"] + ) + ) + } + } + } +} 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 + } + } +} 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 abdc8a38..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,6 +81,22 @@ namespace margelo::nitro::image { return __promise; }(); } + 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) { + 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..2186e1c3 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, 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 e0e1c6eb..fc4f641e 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, options: AssetImageLoadOptions?): Promise + @DoNotStrip @Keep abstract fun loadFromFile(filePath: String): HybridImageSpec 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 c788f948..fd9e0b39 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 declaration of `ResizeMode` to properly resolve imports. namespace margelo::nitro::image { enum class ResizeMode; } @@ -38,12 +44,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 "ResizeMode.hpp" #include #include @@ -313,6 +322,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 ce21bee5..d1055892 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,10 +28,14 @@ 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; } // Forward declaration of `ResizeMode` to properly resolve imports. namespace margelo::nitro::image { enum class ResizeMode; } // Include C++ defined types +#include "AspectFit.hpp" +#include "AssetImageLoadOptions.hpp" #include "AsyncImageLoadOptions.hpp" #include "AsyncImagePriority.hpp" #include "HybridImageFactorySpec.hpp" @@ -35,6 +43,7 @@ namespace margelo::nitro::image { enum class ResizeMode; } #include "HybridImageUtilsSpec.hpp" #include "HybridNitroImageViewSpec.hpp" #include "ImageFormat.hpp" +#include "ImageSize.hpp" #include "ResizeMode.hpp" #include #include diff --git a/nitrogen/generated/ios/c++/HybridImageFactorySpecSwift.hpp b/nitrogen/generated/ios/c++/HybridImageFactorySpecSwift.hpp index 0c961b62..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,6 +89,14 @@ namespace margelo::nitro::image { auto __value = std::move(__result.value()); return __value; } + 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()); + } + 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/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 ac62f102..bba2466d 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, 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 1ed03434..79c1c32d 100644 --- a/nitrogen/generated/ios/swift/HybridImageFactorySpec_cxx.swift +++ b/nitrogen/generated/ios/swift/HybridImageFactorySpec_cxx.swift @@ -128,6 +128,34 @@ public class HybridImageFactorySpec_cxx { } } + @inline(__always) + 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), 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) + __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/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.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..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,6 +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, 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/ImageFactory.ts b/src/ImageFactory.ts index e2bf3f42..64c137b9 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 8dc07050..b3c2e9fe 100644 --- a/src/specs/ImageFactory.nitro.ts +++ b/src/specs/ImageFactory.nitro.ts @@ -56,6 +56,32 @@ 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" }> { /** @@ -69,6 +95,16 @@ export interface ImageFactory 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, + 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.