From a48ba39d743a2df2b90ba568fce0dcf2adefb019 Mon Sep 17 00:00:00 2001 From: Erich Graham Date: Thu, 12 Jan 2023 10:51:11 -0800 Subject: [PATCH] xplat (#4439) Summary: X-link: https://github.com/facebook/flipper/pull/4439 X-link: https://github.com/facebook/hermes/pull/888 X-link: https://github.com/facebook/yoga/pull/1212 Reviewed By: ebgraham Differential Revision: D42472880 fbshipit-source-id: ef04842977deafee0b17af7418eb357c872ebaa9 --- .../ios/Audio/Audio.swift | 92 +- .../ios/Audio/AudioModule.swift | 356 +++--- .../ios/Audio/IAudio.swift | 10 +- .../ios/Camera/CameraManager.swift | 4 +- .../ios/Camera/CameraView.swift | 542 ++++----- .../ios/Camera/CaptureButton.swift | 2 +- .../ios/Canvas/CGContextExtension.swift | 16 +- .../ios/Canvas/CanvasManager.swift | 16 +- .../ios/Canvas/CanvasRenderingContext2D.swift | 1046 ++++++++--------- .../ios/Canvas/DrawingCanvasView.swift | 1046 ++++++++--------- .../ios/Canvas/ImageData.swift | 36 +- .../ios/Canvas/ImageDataModule.swift | 32 +- .../ios/Canvas/LayerData.swift | 67 +- .../ios/Canvas/ShapeLayer.swift | 9 +- .../ios/Image/BitmapImage.swift | 94 +- .../ios/Image/IImage.swift | 16 +- .../ios/Image/Image.swift | 114 +- .../ios/Image/ImageDataImage.swift | 66 +- .../ios/Image/ImageModule.swift | 298 ++--- .../ios/Image/ImageUtils.swift | 44 +- .../ios/Image/UIImage+Orientation.swift | 26 +- .../ios/Image/UIImage+Resize.swift | 18 +- .../ios/JavaScript/JSContext.swift | 126 +- .../ios/JavaScript/JSContextUtils.swift | 12 +- .../ios/ML/ModelLoaderModule.swift | 32 +- .../ios/ML/ModelUtils.swift | 74 +- .../ios/Media/MediaToBlob.swift | 86 +- 27 files changed, 2139 insertions(+), 2141 deletions(-) diff --git a/react-native-pytorch-core/ios/Audio/Audio.swift b/react-native-pytorch-core/ios/Audio/Audio.swift index f7f90c27d..d01a66512 100644 --- a/react-native-pytorch-core/ios/Audio/Audio.swift +++ b/react-native-pytorch-core/ios/Audio/Audio.swift @@ -5,66 +5,66 @@ * LICENSE file in the root directory of this source tree. */ -import Foundation import AVFoundation +import Foundation @objc(PTLAudio) public class Audio: NSObject, IAudio { - private var mPlayer: AVAudioPlayer? - private var mData: Data + private var mPlayer: AVAudioPlayer? + private var mData: Data - private static let DEFAULTVOLUME: Float = 1.0 + private static let DEFAULTVOLUME: Float = 1.0 - @objc - public init(audioData: Data) { - self.mData = audioData - } + @objc + public init(audioData: Data) { + self.mData = audioData + } - public func getData() -> Data { - return self.mData - } + public func getData() -> Data { + return self.mData + } - @objc - public func play() { - do { - if self.mPlayer == nil { - mPlayer = try AVAudioPlayer(data: mData) - } - try? AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category.playback) - mPlayer?.volume = Audio.DEFAULTVOLUME - mPlayer?.play() - } catch { - print("Error while playing the audio. \(error)") - } + @objc + public func play() { + do { + if self.mPlayer == nil { + mPlayer = try AVAudioPlayer(data: mData) + } + try? AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category.playback) + mPlayer?.volume = Audio.DEFAULTVOLUME + mPlayer?.play() + } catch { + print("Error while playing the audio. \(error)") } + } - @objc - public func pause() { - if (mPlayer?.isPlaying) != nil { - mPlayer?.pause() - } + @objc + public func pause() { + if (mPlayer?.isPlaying) != nil { + mPlayer?.pause() } + } - @objc - public func stop() { - if let mPlayer = mPlayer, mPlayer.isPlaying { - mPlayer.stop() - mPlayer.currentTime = 0 - } + @objc + public func stop() { + if let mPlayer = mPlayer, mPlayer.isPlaying { + mPlayer.stop() + mPlayer.currentTime = 0 } + } - @objc - public func getDuration() -> Int { - do { - if self.mPlayer == nil { - mPlayer = try AVAudioPlayer(data: mData) - try? AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category.playback) - } - return Int(mPlayer!.duration * 1000) - } catch { - print("Error while fetching duration of audio. \(error)") - return -1 - } + @objc + public func getDuration() -> Int { + do { + if self.mPlayer == nil { + mPlayer = try AVAudioPlayer(data: mData) + try? AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category.playback) + } + return Int(mPlayer!.duration * 1000) + } catch { + print("Error while fetching duration of audio. \(error)") + return -1 } + } } diff --git a/react-native-pytorch-core/ios/Audio/AudioModule.swift b/react-native-pytorch-core/ios/Audio/AudioModule.swift index c675767e3..20a6ddc1e 100644 --- a/react-native-pytorch-core/ios/Audio/AudioModule.swift +++ b/react-native-pytorch-core/ios/Audio/AudioModule.swift @@ -5,8 +5,8 @@ * LICENSE file in the root directory of this source tree. */ -import Foundation import AVFoundation +import Foundation import UIKit var audioRecorder: AVAudioRecorder! @@ -14,199 +14,199 @@ var audioRecorder: AVAudioRecorder! @objc(AudioModule) public class AudioModule: NSObject, AVAudioRecorderDelegate { - var promiseResolve: RCTPromiseResolveBlock! - var promiseReject: RCTPromiseRejectBlock! - - private static let PREFIX = "audio" - private static let EXTENSION = ".wav" - - enum AudioModuleError: Error { - case castingObject - case castingDict + var promiseResolve: RCTPromiseResolveBlock! + var promiseReject: RCTPromiseRejectBlock! + + private static let PREFIX = "audio" + private static let EXTENSION = ".wav" + + enum AudioModuleError: Error { + case castingObject + case castingDict + } + + @objc + public func getName() -> String { + return "PyTorchCoreAudioModule" + } + + @objc(isRecording:rejecter:) + public func isRecording(_ resolve: @escaping InternalRCTPromiseResolveBlock, + rejecter reject: @escaping InternalRCTPromiseRejectBlock) { + resolve(audioRecorder?.isRecording) + } + + @objc + public func startRecord() { + AVAudioSession.sharedInstance().requestRecordPermission({(granted: Bool) -> Void in + if !granted { + fatalError("Record permission needs to be granted for the app to run successfully.") + } + }) + + let audioSession = AVAudioSession.sharedInstance() + + do { + try audioSession.setCategory(AVAudioSession.Category.record) + try audioSession.setActive(true) + } catch { + fatalError("Error while recording audio.") } - @objc - public func getName() -> String { - return "PyTorchCoreAudioModule" + let settings = [ + AVFormatIDKey: Int(kAudioFormatLinearPCM), + AVSampleRateKey: 16000, + AVNumberOfChannelsKey: 1, + AVLinearPCMBitDepthKey: 16, + AVLinearPCMIsBigEndianKey: false, + AVLinearPCMIsFloatKey: false, + AVEncoderAudioQualityKey: AVAudioQuality.high.rawValue + ] as [String: Any] + do { + let recorderFilePath = (NSHomeDirectory() as NSString).appendingPathComponent("tmp/recorded_file.wav") + audioRecorder = try AVAudioRecorder(url: NSURL.fileURL(withPath: recorderFilePath), settings: settings) + audioRecorder.delegate = self + audioRecorder.record() + } catch let error { + fatalError("Error while recording audio : " + error.localizedDescription) } - - @objc(isRecording:rejecter:) - public func isRecording(_ resolve: @escaping InternalRCTPromiseResolveBlock, - rejecter reject: @escaping InternalRCTPromiseRejectBlock) { - resolve(audioRecorder?.isRecording) + } + + @objc(stopRecord:rejecter:) + public func stopRecord(_ resolve: @escaping InternalRCTPromiseResolveBlock, + rejecter reject: @escaping InternalRCTPromiseRejectBlock) { + promiseResolve = resolve + promiseReject = reject + if audioRecorder.isRecording { + audioRecorder.stop() + } else { + promiseResolve(nil) } + } - @objc - public func startRecord() { - AVAudioSession.sharedInstance().requestRecordPermission({(granted: Bool) -> Void in - if !granted { - fatalError("Record permission needs to be granted for the app to run successfully.") - } - }) - - let audioSession = AVAudioSession.sharedInstance() - - do { - try audioSession.setCategory(AVAudioSession.Category.record) - try audioSession.setActive(true) - } catch { - fatalError("Error while recording audio.") - } - - let settings = [ - AVFormatIDKey: Int(kAudioFormatLinearPCM), - AVSampleRateKey: 16000, - AVNumberOfChannelsKey: 1, - AVLinearPCMBitDepthKey: 16, - AVLinearPCMIsBigEndianKey: false, - AVLinearPCMIsFloatKey: false, - AVEncoderAudioQualityKey: AVAudioQuality.high.rawValue - ] as [String: Any] - do { - let recorderFilePath = (NSHomeDirectory() as NSString).appendingPathComponent("tmp/recorded_file.wav") - audioRecorder = try AVAudioRecorder(url: NSURL.fileURL(withPath: recorderFilePath), settings: settings) - audioRecorder.delegate = self - audioRecorder.record() - } catch let error { - fatalError("Error while recording audio : " + error.localizedDescription) - } - } - - @objc(stopRecord:rejecter:) - public func stopRecord(_ resolve: @escaping InternalRCTPromiseResolveBlock, - rejecter reject: @escaping InternalRCTPromiseRejectBlock) { - promiseResolve = resolve - promiseReject = reject - if audioRecorder.isRecording { - audioRecorder.stop() - } else { - promiseResolve(nil) - } - } - - @objc public func audioRecorderDidFinishRecording(_ recorder: AVAudioRecorder, successfully flag: Bool) { - let recorderFilePath = (NSHomeDirectory() as NSString).appendingPathComponent("tmp/recorded_file.wav") - if flag { - let url = NSURL.fileURL(withPath: recorderFilePath) - guard let data = try? Data(contentsOf: url) else { - audioRecorder.deleteRecording() - return - } - if data.isEmpty { - promiseReject(RCTErrorUnspecified, "Invalid audio data", nil) - } else { - let audio = Audio(audioData: data) - promiseResolve(JSContext.wrapObject(object: audio).getJSRef()) - } - } + @objc public func audioRecorderDidFinishRecording(_ recorder: AVAudioRecorder, successfully flag: Bool) { + let recorderFilePath = (NSHomeDirectory() as NSString).appendingPathComponent("tmp/recorded_file.wav") + if flag { + let url = NSURL.fileURL(withPath: recorderFilePath) + guard let data = try? Data(contentsOf: url) else { audioRecorder.deleteRecording() + return + } + if data.isEmpty { + promiseReject(RCTErrorUnspecified, "Invalid audio data", nil) + } else { + let audio = Audio(audioData: data) + promiseResolve(JSContext.wrapObject(object: audio).getJSRef()) + } } - - @objc - public func play(_ audioRef: NSDictionary) { - do { - let audio = try JSContextUtils.unwrapObject(audioRef, IAudio.self) - audio.play() - } catch { - print("Invalid audio reference sent. \(error)") - } + audioRecorder.deleteRecording() + } + + @objc + public func play(_ audioRef: NSDictionary) { + do { + let audio = try JSContextUtils.unwrapObject(audioRef, IAudio.self) + audio.play() + } catch { + print("Invalid audio reference sent. \(error)") } - - @objc - public func pause(_ audioRef: NSDictionary) { - do { - let audio = try JSContextUtils.unwrapObject(audioRef, IAudio.self) - audio.pause() - } catch { - print("Invalid audio reference sent. \(error)") - } - } - - @objc - public func stop(_ audioRef: NSDictionary) { - do { - let audio = try JSContextUtils.unwrapObject(audioRef, IAudio.self) - audio.stop() - } catch { - print("Invalid audio reference sent. \(error)") - } + } + + @objc + public func pause(_ audioRef: NSDictionary) { + do { + let audio = try JSContextUtils.unwrapObject(audioRef, IAudio.self) + audio.pause() + } catch { + print("Invalid audio reference sent. \(error)") } - - @objc - public func getDuration(_ audioRef: NSDictionary) -> Any { - do { - let audio = try JSContextUtils.unwrapObject(audioRef, IAudio.self) - return audio.getDuration() - } catch { - print("Invalid audio reference sent. \(error)") - return -1 - } + } + + @objc + public func stop(_ audioRef: NSDictionary) { + do { + let audio = try JSContextUtils.unwrapObject(audioRef, IAudio.self) + audio.stop() + } catch { + print("Invalid audio reference sent. \(error)") } - - @objc(toFile:resolver:rejecter:) - public func toFile(_ audioRef: NSDictionary, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) { - let uuid = NSUUID().uuidString - do { - let audio = try JSContextUtils.unwrapObject(audioRef, IAudio.self) - let filename = NSURL.fileURL(withPathComponents: [NSTemporaryDirectory()])!.appendingPathComponent( - AudioModule.PREFIX + uuid + AudioModule.EXTENSION) - try? audio.getData().write(to: filename) - resolve(filename.path) - } catch { - reject(RCTErrorUnspecified, "Could not write audio data to a file. \(error)", error) - } + } + + @objc + public func getDuration(_ audioRef: NSDictionary) -> Any { + do { + let audio = try JSContextUtils.unwrapObject(audioRef, IAudio.self) + return audio.getDuration() + } catch { + print("Invalid audio reference sent. \(error)") + return -1 } - - @objc(fromFile:resolver:rejecter:) - public func fromFile(_ filepath: NSString, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) { - let path = filepath as String - let url = URL(fileURLWithPath: path) - do { - let data = try Data(contentsOf: url) - let ref = JSContext.wrapObject(object: Audio(audioData: data)).getJSRef() - resolve(ref) - } catch { - reject(RCTErrorUnspecified, "Couldn't load file \(path)", nil) - } + } + + @objc(toFile:resolver:rejecter:) + public func toFile(_ audioRef: NSDictionary, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) { + let uuid = NSUUID().uuidString + do { + let audio = try JSContextUtils.unwrapObject(audioRef, IAudio.self) + let filename = NSURL.fileURL(withPathComponents: [NSTemporaryDirectory()])!.appendingPathComponent( + AudioModule.PREFIX + uuid + AudioModule.EXTENSION) + try? audio.getData().write(to: filename) + resolve(filename.path) + } catch { + reject(RCTErrorUnspecified, "Could not write audio data to a file. \(error)", error) } - - @objc(fromBundle:resolver:rejecter:) - public func fromBundle(_ assetAudio: NSString, resolve: @escaping InternalRCTPromiseResolveBlock, - reject: @escaping InternalRCTPromiseRejectBlock) { - let audioUrl = URL(string: assetAudio as String) - let sessionConfig = URLSessionConfiguration.default - let session = URLSession(configuration: sessionConfig) - let request = URLRequest(url: audioUrl!) - let task = session.downloadTask(with: request) { (tempUrl, _, error) in - if let destinationUrl = tempUrl, error == nil { - do { - let data = try Data(contentsOf: destinationUrl) - let ref = JSContext.wrapObject(object: Audio(audioData: data)).getJSRef() - resolve(ref) - } catch { - reject(RCTErrorUnspecified, "Couldn't load audio from asset \(assetAudio)", error) - } - } - } - task.resume() + } + + @objc(fromFile:resolver:rejecter:) + public func fromFile(_ filepath: NSString, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) { + let path = filepath as String + let url = URL(fileURLWithPath: path) + do { + let data = try Data(contentsOf: url) + let ref = JSContext.wrapObject(object: Audio(audioData: data)).getJSRef() + resolve(ref) + } catch { + reject(RCTErrorUnspecified, "Couldn't load file \(path)", nil) } - - @objc(release:resolver:rejecter:) - public func release(_ audioRef: NSDictionary, resolver resolve: RCTPromiseResolveBlock, - rejecter reject: RCTPromiseRejectBlock) { + } + + @objc(fromBundle:resolver:rejecter:) + public func fromBundle(_ assetAudio: NSString, resolve: @escaping InternalRCTPromiseResolveBlock, + reject: @escaping InternalRCTPromiseRejectBlock) { + let audioUrl = URL(string: assetAudio as String) + let sessionConfig = URLSessionConfiguration.default + let session = URLSession(configuration: sessionConfig) + let request = URLRequest(url: audioUrl!) + let task = session.downloadTask(with: request) { (tempUrl, _, error) in + if let destinationUrl = tempUrl, error == nil { do { - if let audioRef = audioRef as? [ String: String] { - try JSContext.release(jsRef: audioRef) - } - resolve(nil) + let data = try Data(contentsOf: destinationUrl) + let ref = JSContext.wrapObject(object: Audio(audioData: data)).getJSRef() + resolve(ref) } catch { - reject(RCTErrorUnspecified, "Invalid audio reference in release: \(error)", error) + reject(RCTErrorUnspecified, "Couldn't load audio from asset \(assetAudio)", error) } + } } - - @objc - public static func wrapAudio(_ audio: Audio) -> NSString { - let ref = JSContext.wrapObject(object: audio).getJSRef() - return ref["ID"]! as NSString + task.resume() + } + + @objc(release:resolver:rejecter:) + public func release(_ audioRef: NSDictionary, resolver resolve: RCTPromiseResolveBlock, + rejecter reject: RCTPromiseRejectBlock) { + do { + if let audioRef = audioRef as? [ String: String] { + try JSContext.release(jsRef: audioRef) + } + resolve(nil) + } catch { + reject(RCTErrorUnspecified, "Invalid audio reference in release: \(error)", error) } + } + + @objc + public static func wrapAudio(_ audio: Audio) -> NSString { + let ref = JSContext.wrapObject(object: audio).getJSRef() + return ref["ID"]! as NSString + } } diff --git a/react-native-pytorch-core/ios/Audio/IAudio.swift b/react-native-pytorch-core/ios/Audio/IAudio.swift index a9935dfb4..47bc2319c 100644 --- a/react-native-pytorch-core/ios/Audio/IAudio.swift +++ b/react-native-pytorch-core/ios/Audio/IAudio.swift @@ -8,13 +8,13 @@ import Foundation public protocol IAudio { - func getData() -> Data + func getData() -> Data - func play() + func play() - func pause() + func pause() - func stop() + func stop() - func getDuration() -> Int + func getDuration() -> Int } diff --git a/react-native-pytorch-core/ios/Camera/CameraManager.swift b/react-native-pytorch-core/ios/Camera/CameraManager.swift index 0bfaa2ab0..630f51c27 100644 --- a/react-native-pytorch-core/ios/Camera/CameraManager.swift +++ b/react-native-pytorch-core/ios/Camera/CameraManager.swift @@ -19,7 +19,7 @@ class CameraManager: RCTViewManager { @objc func takePicture(_ reactTag: NSNumber) { self.bridge!.uiManager.addUIBlock { (_: RCTUIManager?, viewRegistry: [NSNumber: UIView]?) in guard let view: CameraView = viewRegistry?[reactTag] as? CameraView else { - return + return } view.captureImage() } @@ -28,7 +28,7 @@ class CameraManager: RCTViewManager { @objc func flip(_ reactTag: NSNumber) { self.bridge!.uiManager.addUIBlock { (_: RCTUIManager?, viewRegistry: [NSNumber: UIView]?) in guard let view: CameraView = viewRegistry?[reactTag] as? CameraView else { - return + return } view.flipCamera() } diff --git a/react-native-pytorch-core/ios/Camera/CameraView.swift b/react-native-pytorch-core/ios/Camera/CameraView.swift index 1a7ccf8d8..8fa991c25 100644 --- a/react-native-pytorch-core/ios/Camera/CameraView.swift +++ b/react-native-pytorch-core/ios/Camera/CameraView.swift @@ -5,323 +5,323 @@ * LICENSE file in the root directory of this source tree. */ -import Foundation import AVFoundation +import Foundation import UIKit import VideoToolbox @objc(CameraView) class CameraView: UIView, AVCapturePhotoCaptureDelegate, AVCaptureVideoDataOutputSampleBufferDelegate { - private let captureButtonSize = 75.0 - private let captureButtonMargin = 16.0 - private let flipCameraButtonSize = 36.0 - private let flipCameraButtonMargin = 30.0 - private let photoOutput = AVCapturePhotoOutput() - private var videoOutput: AVCaptureVideoDataOutput! - private var captureSession: AVCaptureSession! - private var cameraLayer: AVCaptureVideoPreviewLayer! - private var backCamera: AVCaptureDevice! - private var backInput: AVCaptureInput! - private var frontCamera: AVCaptureDevice! - private var frontInput: AVCaptureInput! - private var previousImageFrame: Image? - @objc public var onCapture: RCTDirectEventBlock? - @objc public var onFrame: RCTDirectEventBlock? - @objc public var hideCaptureButton: Bool = false - @objc public var hideFlipButton: Bool = false - @objc public var targetResolution: NSDictionary = [ "width": 480, "height": 640] - var captureButton: CaptureButton? - var flipCameraButton: UIButton? - var backCameraOn = true + private let captureButtonSize = 75.0 + private let captureButtonMargin = 16.0 + private let flipCameraButtonSize = 36.0 + private let flipCameraButtonMargin = 30.0 + private let photoOutput = AVCapturePhotoOutput() + private var videoOutput: AVCaptureVideoDataOutput! + private var captureSession: AVCaptureSession! + private var cameraLayer: AVCaptureVideoPreviewLayer! + private var backCamera: AVCaptureDevice! + private var backInput: AVCaptureInput! + private var frontCamera: AVCaptureDevice! + private var frontInput: AVCaptureInput! + private var previousImageFrame: Image? + @objc public var onCapture: RCTDirectEventBlock? + @objc public var onFrame: RCTDirectEventBlock? + @objc public var hideCaptureButton: Bool = false + @objc public var hideFlipButton: Bool = false + @objc public var targetResolution: NSDictionary = [ "width": 480, "height": 640] + var captureButton: CaptureButton? + var flipCameraButton: UIButton? + var backCameraOn = true - public func openCamera() { - setupCaptureButton() - setupFlipCameraButton() - switch AVCaptureDevice.authorizationStatus(for: .video) { - case .authorized: + public func openCamera() { + setupCaptureButton() + setupFlipCameraButton() + switch AVCaptureDevice.authorizationStatus(for: .video) { + case .authorized: + self.setupCaptureSession() + case .notDetermined: + AVCaptureDevice.requestAccess(for: .video) { (granted) in + if granted { + DispatchQueue.main.async { self.setupCaptureSession() - case .notDetermined: - AVCaptureDevice.requestAccess(for: .video) { (granted) in - if granted { - DispatchQueue.main.async { - self.setupCaptureSession() - } - } else { - self.createErrorView(error: "Camera permission not granted") - } - } - case .denied: - createErrorView(error: "Camera access previously denied") - case .restricted: - createErrorView(error: "Camera access restricted") - default: - createErrorView(error: "Cannot access camera permission status") + } + } else { + self.createErrorView(error: "Camera permission not granted") } + } + case .denied: + createErrorView(error: "Camera access previously denied") + case .restricted: + createErrorView(error: "Camera access restricted") + default: + createErrorView(error: "Cannot access camera permission status") } + } - override func didSetProps(_ changedProps: [String]!) { - captureButton?.isHidden = hideCaptureButton - flipCameraButton?.isHidden = hideFlipButton - if changedProps.contains("targetResolution") { setSessionPreset() } - } + override func didSetProps(_ changedProps: [String]!) { + captureButton?.isHidden = hideCaptureButton + flipCameraButton?.isHidden = hideFlipButton + if changedProps.contains("targetResolution") { setSessionPreset() } + } - override func layoutSubviews() { - let height = self.bounds.height - let width = self.bounds.width + override func layoutSubviews() { + let height = self.bounds.height + let width = self.bounds.width - cameraLayer?.frame = CGRect(x: 0, y: 0, width: width, height: height) + cameraLayer?.frame = CGRect(x: 0, y: 0, width: width, height: height) - let captureButtonX = Double(width) / 2.0 - captureButtonSize / 2.0 - let captureButtonY = Double(height) - captureButtonSize - captureButtonMargin + let captureButtonX = Double(width) / 2.0 - captureButtonSize / 2.0 + let captureButtonY = Double(height) - captureButtonSize - captureButtonMargin - captureButton?.frame = CGRect(x: captureButtonX, - y: captureButtonY, - width: captureButtonSize, - height: captureButtonSize) + captureButton?.frame = CGRect(x: captureButtonX, + y: captureButtonY, + width: captureButtonSize, + height: captureButtonSize) - let flipCameraButtonX = Double(width) - flipCameraButtonMargin - flipCameraButtonSize - let flipCameraButtonY = Double(height) - - captureButtonMargin - - (captureButtonSize - flipCameraButtonSize) / 2.0 - - flipCameraButtonSize + let flipCameraButtonX = Double(width) - flipCameraButtonMargin - flipCameraButtonSize + let flipCameraButtonY = Double(height) - + captureButtonMargin - + (captureButtonSize - flipCameraButtonSize) / 2.0 - + flipCameraButtonSize - flipCameraButton?.frame = CGRect(x: flipCameraButtonX, - y: flipCameraButtonY, - width: flipCameraButtonSize, - height: flipCameraButtonSize) - captureButton?.isHidden = hideCaptureButton - flipCameraButton?.isHidden = hideFlipButton - } + flipCameraButton?.frame = CGRect(x: flipCameraButtonX, + y: flipCameraButtonY, + width: flipCameraButtonSize, + height: flipCameraButtonSize) + captureButton?.isHidden = hideCaptureButton + flipCameraButton?.isHidden = hideFlipButton + } - func setSessionPreset() { - if let captureSession = captureSession, - let width = (targetResolution["width"] as? NSNumber)?.intValue, - let height = (targetResolution["height"] as? NSNumber)?.intValue { - if width <= 288 && height <= 352 { - captureSession.sessionPreset = .cif352x288 - } else if width <= 480 && height <= 640 { - captureSession.sessionPreset = .vga640x480 - } else if width <= 720 && height <= 1280 { - captureSession.sessionPreset = .hd1280x720 - } else if width <= 1080 && height <= 1920 { - captureSession.sessionPreset = .hd1920x1080 - } else { - captureSession.sessionPreset = .hd4K3840x2160 - } - } + func setSessionPreset() { + if let captureSession = captureSession, + let width = (targetResolution["width"] as? NSNumber)?.intValue, + let height = (targetResolution["height"] as? NSNumber)?.intValue { + if width <= 288 && height <= 352 { + captureSession.sessionPreset = .cif352x288 + } else if width <= 480 && height <= 640 { + captureSession.sessionPreset = .vga640x480 + } else if width <= 720 && height <= 1280 { + captureSession.sessionPreset = .hd1280x720 + } else if width <= 1080 && height <= 1920 { + captureSession.sessionPreset = .hd1920x1080 + } else { + captureSession.sessionPreset = .hd4K3840x2160 + } } + } - func setupCaptureButton() { - let xPos = Double(self.bounds.width) / 2.0 - captureButtonSize / 2.0 - let yPos = Double(self.bounds.height) - captureButtonSize - captureButtonMargin - let frame = CGRect(x: xPos, y: yPos, width: captureButtonSize, height: captureButtonSize) - captureButton = CaptureButton(frame: frame) - captureButton?.isHidden = hideCaptureButton - captureButton?.addTarget(self, action: #selector(captureImage), for: .touchDown) - if let captureButton = captureButton { - self.addSubview(captureButton) - } + func setupCaptureButton() { + let xPos = Double(self.bounds.width) / 2.0 - captureButtonSize / 2.0 + let yPos = Double(self.bounds.height) - captureButtonSize - captureButtonMargin + let frame = CGRect(x: xPos, y: yPos, width: captureButtonSize, height: captureButtonSize) + captureButton = CaptureButton(frame: frame) + captureButton?.isHidden = hideCaptureButton + captureButton?.addTarget(self, action: #selector(captureImage), for: .touchDown) + if let captureButton = captureButton { + self.addSubview(captureButton) } + } - func setupFlipCameraButton() { - if flipCameraButton?.superview != nil { - flipCameraButton?.removeFromSuperview() - } + func setupFlipCameraButton() { + if flipCameraButton?.superview != nil { + flipCameraButton?.removeFromSuperview() + } - let xPos = Double(self.bounds.width) - flipCameraButtonMargin - let yPos = Double(self.bounds.height) - - captureButtonMargin - - (captureButtonSize - flipCameraButtonSize) / 2.0 - - flipCameraButtonSize + let xPos = Double(self.bounds.width) - flipCameraButtonMargin + let yPos = Double(self.bounds.height) - + captureButtonMargin - + (captureButtonSize - flipCameraButtonSize) / 2.0 - + flipCameraButtonSize - flipCameraButton = UIButton() - flipCameraButton?.tintColor = UIColor.white - flipCameraButton?.frame = CGRect(x: xPos, - y: yPos, - width: flipCameraButtonSize, - height: flipCameraButtonSize) - flipCameraButton?.isHidden = hideFlipButton - flipCameraButton?.addTarget(self, action: #selector(flipCamera), for: .touchDown) + flipCameraButton = UIButton() + flipCameraButton?.tintColor = UIColor.white + flipCameraButton?.frame = CGRect(x: xPos, + y: yPos, + width: flipCameraButtonSize, + height: flipCameraButtonSize) + flipCameraButton?.isHidden = hideFlipButton + flipCameraButton?.addTarget(self, action: #selector(flipCamera), for: .touchDown) - let bundle = Bundle(for: CameraView.self) - if let url = bundle.url(forResource: "PyTorchCore", withExtension: "bundle") { - let coreBundle = Bundle(url: url) - let flipCameraImage = UIImage(named: "FlipCamera", in: coreBundle, compatibleWith: nil) - flipCameraButton?.setImage(flipCameraImage, for: .normal) - } + let bundle = Bundle(for: CameraView.self) + if let url = bundle.url(forResource: "PyTorchCore", withExtension: "bundle") { + let coreBundle = Bundle(url: url) + let flipCameraImage = UIImage(named: "FlipCamera", in: coreBundle, compatibleWith: nil) + flipCameraButton?.setImage(flipCameraImage, for: .normal) + } - if let flipCameraButton = flipCameraButton { - self.addSubview(flipCameraButton) - } + if let flipCameraButton = flipCameraButton { + self.addSubview(flipCameraButton) } + } - func setupInput() { - if #available(iOS 10.2, *) { - if let device = AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInDualCamera, .builtInWideAngleCamera], - mediaType: .video, - position: .back).devices.first { - backCamera = device - if let bInput = try? AVCaptureDeviceInput(device: backCamera) { - backInput = bInput - captureSession.addInput(backInput) - } else { - createErrorView(error: "Could not add output to capture session") - } - } else { - createErrorView(error: "Could not find back camera") - } + func setupInput() { + if #available(iOS 10.2, *) { + if let device = AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInDualCamera, .builtInWideAngleCamera], + mediaType: .video, + position: .back).devices.first { + backCamera = device + if let bInput = try? AVCaptureDeviceInput(device: backCamera) { + backInput = bInput + captureSession.addInput(backInput) } else { - if let device = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back) { - backCamera = device - if let bInput = try? AVCaptureDeviceInput(device: backCamera) { - backInput = bInput - captureSession.addInput(backInput) - } else { - createErrorView(error: "Could not add output to capture session") - } - } else { - createErrorView(error: "Could not find back camera") - } + createErrorView(error: "Could not add output to capture session") } - - if let device = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .front) { - frontCamera = device - if let fInput = try? AVCaptureDeviceInput(device: frontCamera) { - frontInput = fInput - } else { - print("could not create front input") - } + } else { + createErrorView(error: "Could not find back camera") + } + } else { + if let device = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back) { + backCamera = device + if let bInput = try? AVCaptureDeviceInput(device: backCamera) { + backInput = bInput + captureSession.addInput(backInput) } else { - print("no front camera") + createErrorView(error: "Could not add output to capture session") } + } else { + createErrorView(error: "Could not find back camera") + } } - func setupOutput() { - videoOutput = AVCaptureVideoDataOutput() - let videoQueue = DispatchQueue(label: "videoQueue", qos: .userInteractive) - videoOutput.setSampleBufferDelegate(self, queue: videoQueue) - if captureSession.canAddOutput(videoOutput) { - captureSession.addOutput(videoOutput) - } - videoOutput.connection(with: AVMediaType.video)?.videoOrientation = .portrait - if captureSession.canAddOutput(photoOutput) { - captureSession.addOutput(photoOutput) - } + if let device = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .front) { + frontCamera = device + if let fInput = try? AVCaptureDeviceInput(device: frontCamera) { + frontInput = fInput + } else { + print("could not create front input") + } + } else { + print("no front camera") } + } - func setupPreviewLayer() { - cameraLayer = AVCaptureVideoPreviewLayer(session: captureSession) - cameraLayer.videoGravity = .resizeAspectFill - self.layer.insertSublayer(cameraLayer, at: 0) + func setupOutput() { + videoOutput = AVCaptureVideoDataOutput() + let videoQueue = DispatchQueue(label: "videoQueue", qos: .userInteractive) + videoOutput.setSampleBufferDelegate(self, queue: videoQueue) + if captureSession.canAddOutput(videoOutput) { + captureSession.addOutput(videoOutput) } - - private func setupCaptureSession() { - self.captureSession = AVCaptureSession() - self.captureSession.beginConfiguration() - if self.captureSession.canSetSessionPreset(.photo) { - self.captureSession.sessionPreset = .photo - } else { - createErrorView(error: "capture device not working") - } - self.setupInput() - self.setupPreviewLayer() - self.setupOutput() - self.setSessionPreset() - self.captureSession.commitConfiguration() - self.captureSession.startRunning() + videoOutput.connection(with: AVMediaType.video)?.videoOrientation = .portrait + if captureSession.canAddOutput(photoOutput) { + captureSession.addOutput(photoOutput) } + } - private func createErrorView(error: String) { - let label = UILabel(frame: self.frame) - label.text = error - self.addSubview(label) - } + func setupPreviewLayer() { + cameraLayer = AVCaptureVideoPreviewLayer(session: captureSession) + cameraLayer.videoGravity = .resizeAspectFill + self.layer.insertSublayer(cameraLayer, at: 0) + } - @objc func flipCamera() { - flipCameraButton?.isUserInteractionEnabled = false - captureSession.beginConfiguration() - if backCameraOn { - captureSession.removeInput(backInput) - captureSession.addInput(frontInput) - backCameraOn = false - } else { - captureSession.removeInput(frontInput) - captureSession.addInput(backInput) - backCameraOn = true - } - videoOutput.connection(with: AVMediaType.video)?.videoOrientation = .portrait - captureSession.commitConfiguration() - flipCameraButton?.isUserInteractionEnabled = true + private func setupCaptureSession() { + self.captureSession = AVCaptureSession() + self.captureSession.beginConfiguration() + if self.captureSession.canSetSessionPreset(.photo) { + self.captureSession.sessionPreset = .photo + } else { + createErrorView(error: "capture device not working") } + self.setupInput() + self.setupPreviewLayer() + self.setupOutput() + self.setSessionPreset() + self.captureSession.commitConfiguration() + self.captureSession.startRunning() + } + + private func createErrorView(error: String) { + let label = UILabel(frame: self.frame) + label.text = error + self.addSubview(label) + } - @objc func captureImage() { - let photoSettings = - AVCapturePhotoSettings(format: [kCVPixelBufferPixelFormatTypeKey as String: Int(kCVPixelFormatType_32BGRA)]) - self.photoOutput.capturePhoto(with: photoSettings, delegate: self) + @objc func flipCamera() { + flipCameraButton?.isUserInteractionEnabled = false + captureSession.beginConfiguration() + if backCameraOn { + captureSession.removeInput(backInput) + captureSession.addInput(frontInput) + backCameraOn = false + } else { + captureSession.removeInput(frontInput) + captureSession.addInput(backInput) + backCameraOn = true } + videoOutput.connection(with: AVMediaType.video)?.videoOrientation = .portrait + captureSession.commitConfiguration() + flipCameraButton?.isUserInteractionEnabled = true + } - @available(iOS 11.0, *) - func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) { - if let error = error { - print("Error capturing photo: \(error)") - return - } - guard let image: CGImage = photo.cgImageRepresentation() else { print("Could not get imageData"); return } - let colorSpace: CGColorSpace = image.colorSpace ?? CGColorSpace(name: CGColorSpace.sRGB)! - guard let context = CGContext( - data: nil, - width: image.height, - height: image.width, - bitsPerComponent: image.bitsPerComponent, - bytesPerRow: image.bytesPerRow, - space: colorSpace, - bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue) - else { - print("Could not create context to rotate image, resolving with unrotated image") - let bitmapImage = Image(image: image) - let ref = JSContext.wrapObject(object: bitmapImage).getJSRef() - if let onCapture = onCapture { - onCapture(ref) - } - return - } - context.translateBy(x: CGFloat(image.width)/2, y: CGFloat(image.height)/2) - context.rotate(by: -CGFloat.pi/2) - context.translateBy(x: CGFloat(image.height)/(-2) - (CGFloat(image.width - image.height)), - y: CGFloat(image.width)/(-2)) - context.draw(image, in: CGRect(origin: .zero, size: CGSize(width: image.width, height: image.height))) - if let rotatedImage = context.makeImage() { - let bitmapImage = Image(image: rotatedImage) - let ref = JSContext.wrapObject(object: bitmapImage).getJSRef() - if let onCapture = onCapture { - onCapture(ref) - } - } else { - print("Error making image") - } + @objc func captureImage() { + let photoSettings = + AVCapturePhotoSettings(format: [kCVPixelBufferPixelFormatTypeKey as String: Int(kCVPixelFormatType_32BGRA)]) + self.photoOutput.capturePhoto(with: photoSettings, delegate: self) + } + + @available(iOS 11.0, *) + func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) { + if let error = error { + print("Error capturing photo: \(error)") + return + } + guard let image: CGImage = photo.cgImageRepresentation() else { print("Could not get imageData"); return } + let colorSpace: CGColorSpace = image.colorSpace ?? CGColorSpace(name: CGColorSpace.sRGB)! + guard let context = CGContext( + data: nil, + width: image.height, + height: image.width, + bitsPerComponent: image.bitsPerComponent, + bytesPerRow: image.bytesPerRow, + space: colorSpace, + bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue) + else { + print("Could not create context to rotate image, resolving with unrotated image") + let bitmapImage = Image(image: image) + let ref = JSContext.wrapObject(object: bitmapImage).getJSRef() + if let onCapture = onCapture { + onCapture(ref) + } + return } + context.translateBy(x: CGFloat(image.width) / 2, y: CGFloat(image.height) / 2) + context.rotate(by: -CGFloat.pi / 2) + context.translateBy(x: CGFloat(image.height) / (-2) - (CGFloat(image.width - image.height)), + y: CGFloat(image.width) / (-2)) + context.draw(image, in: CGRect(origin: .zero, size: CGSize(width: image.width, height: image.height))) + if let rotatedImage = context.makeImage() { + let bitmapImage = Image(image: rotatedImage) + let ref = JSContext.wrapObject(object: bitmapImage).getJSRef() + if let onCapture = onCapture { + onCapture(ref) + } + } else { + print("Error making image") + } + } - func captureOutput(_ output: AVCaptureOutput, - didOutput sampleBuffer: CMSampleBuffer, - from connection: AVCaptureConnection) { - // Check if the previous image frame was released in JavaScript. If the - // image was not released, then the closed flag in the image frame is - // false and the current frame will be skipped to avoid the data - // processing pipeline from congesting. - if previousImageFrame != nil && previousImageFrame!.isClosed() == false { - return - } - guard let onFrame = onFrame else { return } - guard let cvBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { - print("Could not get sample buffer") - return - // TODO(T92857704) Eventually forward Error to React Native using promises - } - let ciImage = CIImage(cvImageBuffer: cvBuffer) - let imageFrame = Image(image: ciImage) - let ref = JSContext.wrapObject(object: imageFrame).getJSRef() - previousImageFrame = imageFrame - onFrame(ref) + func captureOutput(_ output: AVCaptureOutput, + didOutput sampleBuffer: CMSampleBuffer, + from connection: AVCaptureConnection) { + // Check if the previous image frame was released in JavaScript. If the + // image was not released, then the closed flag in the image frame is + // false and the current frame will be skipped to avoid the data + // processing pipeline from congesting. + if previousImageFrame != nil && previousImageFrame!.isClosed() == false { + return + } + guard let onFrame = onFrame else { return } + guard let cvBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { + print("Could not get sample buffer") + return + // TODO(T92857704) Eventually forward Error to React Native using promises } + let ciImage = CIImage(cvImageBuffer: cvBuffer) + let imageFrame = Image(image: ciImage) + let ref = JSContext.wrapObject(object: imageFrame).getJSRef() + previousImageFrame = imageFrame + onFrame(ref) + } } diff --git a/react-native-pytorch-core/ios/Camera/CaptureButton.swift b/react-native-pytorch-core/ios/Camera/CaptureButton.swift index 361eaa4ce..c45c9f11c 100644 --- a/react-native-pytorch-core/ios/Camera/CaptureButton.swift +++ b/react-native-pytorch-core/ios/Camera/CaptureButton.swift @@ -64,7 +64,7 @@ class CaptureButton: UIButton { fillColor: UIColor.white.cgColor) layer.addSublayer(innerCircleLayer!) innerCircleLayer!.bounds = innerCircleBounds - innerCircleLayer!.position = CGPoint(x: self.bounds.midX, y: self.bounds.midY) + innerCircleLayer!.position = CGPoint(x: self.bounds.midX, y: self.bounds.midY) } } diff --git a/react-native-pytorch-core/ios/Canvas/CGContextExtension.swift b/react-native-pytorch-core/ios/Canvas/CGContextExtension.swift index c317946db..973f0daf5 100644 --- a/react-native-pytorch-core/ios/Canvas/CGContextExtension.swift +++ b/react-native-pytorch-core/ios/Canvas/CGContextExtension.swift @@ -9,12 +9,12 @@ import Foundation extension CAShapeLayer { - func setStyle(state: CanvasState) { - self.fillColor = state.fillStyle - self.strokeColor = state.strokeStyle - self.lineWidth = state.lineWidth - self.lineCap = state.lineCap - self.lineJoin = state.lineJoin - self.miterLimit = state.miterLimit - } + func setStyle(state: CanvasState) { + self.fillColor = state.fillStyle + self.strokeColor = state.strokeStyle + self.lineWidth = state.lineWidth + self.lineCap = state.lineCap + self.lineJoin = state.lineJoin + self.miterLimit = state.miterLimit + } } diff --git a/react-native-pytorch-core/ios/Canvas/CanvasManager.swift b/react-native-pytorch-core/ios/Canvas/CanvasManager.swift index e1b72a7c6..1fb2ceb84 100644 --- a/react-native-pytorch-core/ios/Canvas/CanvasManager.swift +++ b/react-native-pytorch-core/ios/Canvas/CanvasManager.swift @@ -10,13 +10,13 @@ import Foundation @objc(CanvasManager) class CanvasManager: RCTViewManager { - override func view() -> UIView! { - let canvas = DrawingCanvasView() - canvas.backgroundColor = .white - return canvas - } + override func view() -> UIView! { + let canvas = DrawingCanvasView() + canvas.backgroundColor = .white + return canvas + } - override static func requiresMainQueueSetup() -> Bool { - return true - } + override static func requiresMainQueueSetup() -> Bool { + return true + } } diff --git a/react-native-pytorch-core/ios/Canvas/CanvasRenderingContext2D.swift b/react-native-pytorch-core/ios/Canvas/CanvasRenderingContext2D.swift index 41cb5c979..90dc40221 100644 --- a/react-native-pytorch-core/ios/Canvas/CanvasRenderingContext2D.swift +++ b/react-native-pytorch-core/ios/Canvas/CanvasRenderingContext2D.swift @@ -10,579 +10,579 @@ import Foundation @objc(CanvasRenderingContext2D) class CanvasRenderingContext2D: NSObject { - enum CanvasRenderingContext2DError: Error { - case castingObject - case castingDict - } + enum CanvasRenderingContext2DError: Error { + case castingObject + case castingDict + } - @objc - func fillRect(_ canvasRef: NSDictionary, + @objc + func fillRect(_ canvasRef: NSDictionary, + x: NSNumber, + y: NSNumber, + width: NSNumber, + height: NSNumber, + resolver resolve: RCTPromiseResolveBlock, + rejecter reject: RCTPromiseRejectBlock) { + do { + let canvasView = try JSContextUtils.unwrapObject(canvasRef, DrawingCanvasView.self) + canvasView.fillRect(x: CGFloat(truncating: x), + y: CGFloat(truncating: y), + width: CGFloat(truncating: width), + height: CGFloat(truncating: height)) + resolve(nil) + } catch { + reject(RCTErrorUnspecified, "Could not perform fillRect: \(error)", error) + } + } + + @objc + func strokeRect(_ canvasRef: NSDictionary, x: NSNumber, y: NSNumber, width: NSNumber, height: NSNumber, resolver resolve: RCTPromiseResolveBlock, rejecter reject: RCTPromiseRejectBlock) { - do { - let canvasView = try JSContextUtils.unwrapObject(canvasRef, DrawingCanvasView.self) - canvasView.fillRect(x: CGFloat(truncating: x), - y: CGFloat(truncating: y), - width: CGFloat(truncating: width), - height: CGFloat(truncating: height)) - resolve(nil) - } catch { - reject(RCTErrorUnspecified, "Could not perform fillRect: \(error)", error) - } - } - - @objc - func strokeRect(_ canvasRef: NSDictionary, - x: NSNumber, - y: NSNumber, - width: NSNumber, - height: NSNumber, - resolver resolve: RCTPromiseResolveBlock, - rejecter reject: RCTPromiseRejectBlock) { - do { - let canvasView = try JSContextUtils.unwrapObject(canvasRef, DrawingCanvasView.self) - canvasView.strokeRect(x: CGFloat(truncating: x), - y: CGFloat(truncating: y), - width: CGFloat(truncating: width), - height: CGFloat(truncating: height)) - resolve(nil) - } catch { - reject(RCTErrorUnspecified, "Could not perform strokeRect: \(error)", error) - } - } - - @objc - func rect(_ canvasRef: NSDictionary, - x: NSNumber, - y: NSNumber, - width: NSNumber, - height: NSNumber, - resolver resolve: RCTPromiseResolveBlock, - rejecter reject: RCTPromiseRejectBlock) { - do { - let canvasView = try JSContextUtils.unwrapObject(canvasRef, DrawingCanvasView.self) - canvasView.rect(x: CGFloat(truncating: x), + do { + let canvasView = try JSContextUtils.unwrapObject(canvasRef, DrawingCanvasView.self) + canvasView.strokeRect(x: CGFloat(truncating: x), y: CGFloat(truncating: y), width: CGFloat(truncating: width), height: CGFloat(truncating: height)) - resolve(nil) - } catch { - reject(RCTErrorUnspecified, "Could not perform rect: \(error)", error) - } + resolve(nil) + } catch { + reject(RCTErrorUnspecified, "Could not perform strokeRect: \(error)", error) + } + } + + @objc + func rect(_ canvasRef: NSDictionary, + x: NSNumber, + y: NSNumber, + width: NSNumber, + height: NSNumber, + resolver resolve: RCTPromiseResolveBlock, + rejecter reject: RCTPromiseRejectBlock) { + do { + let canvasView = try JSContextUtils.unwrapObject(canvasRef, DrawingCanvasView.self) + canvasView.rect(x: CGFloat(truncating: x), + y: CGFloat(truncating: y), + width: CGFloat(truncating: width), + height: CGFloat(truncating: height)) + resolve(nil) + } catch { + reject(RCTErrorUnspecified, "Could not perform rect: \(error)", error) + } + } + + @objc + func arc(_ canvasRef: NSDictionary, + x: NSNumber, + y: NSNumber, + radius: NSNumber, + startAngle: NSNumber, + endAngle: NSNumber, + counterclockwise: Bool, + resolver resolve: RCTPromiseResolveBlock, + rejecter reject: RCTPromiseRejectBlock) { + do { + let canvasView = try JSContextUtils.unwrapObject(canvasRef, DrawingCanvasView.self) + canvasView.arc(x: CGFloat(truncating: x), + y: CGFloat(truncating: y), + radius: CGFloat(truncating: radius), + startAngle: CGFloat(truncating: startAngle), + endAngle: CGFloat(truncating: endAngle), + counterclockwise: counterclockwise) + resolve(nil) + } catch { + reject(RCTErrorUnspecified, "Could not perform arc: \(error)", error) + } + } + + @objc + func clearRect(_ canvasRef: NSDictionary, + x: NSNumber, + y: NSNumber, + width: NSNumber, + height: NSNumber, + resolver resolve: RCTPromiseResolveBlock, + rejecter reject: RCTPromiseRejectBlock) { + do { + let canvasView = try JSContextUtils.unwrapObject(canvasRef, DrawingCanvasView.self) + canvasView.clearRect(x: CGFloat(truncating: x), + y: CGFloat(truncating: y), + width: CGFloat(truncating: width), + height: CGFloat(truncating: height)) + resolve(nil) + } catch { + reject(RCTErrorUnspecified, "Could not perform clearRect: \(error)", error) } + } - @objc - func arc(_ canvasRef: NSDictionary, - x: NSNumber, - y: NSNumber, - radius: NSNumber, - startAngle: NSNumber, - endAngle: NSNumber, - counterclockwise: Bool, + @objc + func clear(_ canvasRef: NSDictionary, resolver resolve: RCTPromiseResolveBlock, rejecter reject: RCTPromiseRejectBlock) { - do { - let canvasView = try JSContextUtils.unwrapObject(canvasRef, DrawingCanvasView.self) - canvasView.arc(x: CGFloat(truncating: x), - y: CGFloat(truncating: y), - radius: CGFloat(truncating: radius), - startAngle: CGFloat(truncating: startAngle), - endAngle: CGFloat(truncating: endAngle), - counterclockwise: counterclockwise) - resolve(nil) - } catch { - reject(RCTErrorUnspecified, "Could not perform arc: \(error)", error) - } - } - - @objc - func clearRect(_ canvasRef: NSDictionary, - x: NSNumber, - y: NSNumber, - width: NSNumber, - height: NSNumber, - resolver resolve: RCTPromiseResolveBlock, - rejecter reject: RCTPromiseRejectBlock) { - do { - let canvasView = try JSContextUtils.unwrapObject(canvasRef, DrawingCanvasView.self) - canvasView.clearRect(x: CGFloat(truncating: x), - y: CGFloat(truncating: y), - width: CGFloat(truncating: width), - height: CGFloat(truncating: height)) - resolve(nil) - } catch { - reject(RCTErrorUnspecified, "Could not perform clearRect: \(error)", error) - } - } - - @objc - func clear(_ canvasRef: NSDictionary, - resolver resolve: RCTPromiseResolveBlock, - rejecter reject: RCTPromiseRejectBlock) { - do { - let canvasView = try JSContextUtils.unwrapObject(canvasRef, DrawingCanvasView.self) - canvasView.clear() - resolve(nil) - } catch { - reject(RCTErrorUnspecified, "Could not perform clear: \(error)", error) - } + do { + let canvasView = try JSContextUtils.unwrapObject(canvasRef, DrawingCanvasView.self) + canvasView.clear() + resolve(nil) + } catch { + reject(RCTErrorUnspecified, "Could not perform clear: \(error)", error) } + } - @objc - func invalidate(_ canvasRef: NSDictionary, - resolver resolve: RCTPromiseResolveBlock, - rejecter reject: RCTPromiseRejectBlock) { - do { - let canvasView = try JSContextUtils.unwrapObject(canvasRef, DrawingCanvasView.self) - canvasView.invalidate() - resolve(nil) - } catch { - reject(RCTErrorUnspecified, "Could not perform invalidate: \(error)", error) - } - } - - @objc - func stroke(_ canvasRef: NSDictionary, - resolver resolve: RCTPromiseResolveBlock, - rejecter reject: RCTPromiseRejectBlock) { - do { - let canvasView = try JSContextUtils.unwrapObject(canvasRef, DrawingCanvasView.self) - canvasView.stroke() - resolve(nil) - } catch { - reject(RCTErrorUnspecified, "Could not perform stroke: \(error)", error) - } + @objc + func invalidate(_ canvasRef: NSDictionary, + resolver resolve: RCTPromiseResolveBlock, + rejecter reject: RCTPromiseRejectBlock) { + do { + let canvasView = try JSContextUtils.unwrapObject(canvasRef, DrawingCanvasView.self) + canvasView.invalidate() + resolve(nil) + } catch { + reject(RCTErrorUnspecified, "Could not perform invalidate: \(error)", error) } + } - @objc - func fill(_ canvasRef: NSDictionary, + @objc + func stroke(_ canvasRef: NSDictionary, resolver resolve: RCTPromiseResolveBlock, rejecter reject: RCTPromiseRejectBlock) { - do { - let canvasView = try JSContextUtils.unwrapObject(canvasRef, DrawingCanvasView.self) - canvasView.fill() - resolve(nil) - } catch { - reject(RCTErrorUnspecified, "Could not perform fill: \(error)", error) - } - } - - @objc - func scale(_ canvasRef: NSDictionary, - x: NSNumber, - y: NSNumber, - resolver resolve: RCTPromiseResolveBlock, - rejecter reject: RCTPromiseRejectBlock) { - do { - let canvasView = try JSContextUtils.unwrapObject(canvasRef, DrawingCanvasView.self) - canvasView.scale(x: CGFloat(truncating: x), y: CGFloat(truncating: y)) - resolve(nil) - } catch { - reject(RCTErrorUnspecified, "Could not perform scale: \(error)", error) - } - } - - @objc - func rotate(_ canvasRef: NSDictionary, - angle: NSNumber, - resolver resolve: RCTPromiseResolveBlock, - rejecter reject: RCTPromiseRejectBlock) { - do { - let canvasView = try JSContextUtils.unwrapObject(canvasRef, DrawingCanvasView.self) - canvasView.rotate(angle: CGFloat(truncating: angle)) - resolve(nil) - } catch { - reject(RCTErrorUnspecified, "Could not perform rotate: \(error)", error) - } - } - - @objc - func translate(_ canvasRef: NSDictionary, - x: NSNumber, - y: NSNumber, - resolver resolve: RCTPromiseResolveBlock, - rejecter reject: RCTPromiseRejectBlock) { - do { - let canvasView = try JSContextUtils.unwrapObject(canvasRef, DrawingCanvasView.self) - canvasView.translate(x: CGFloat(truncating: x), y: CGFloat(truncating: y)) - resolve(nil) - } catch { - reject(RCTErrorUnspecified, "Could not perform translate: \(error)", error) - } - } - - @objc - func setTransform(_ canvasRef: NSDictionary, - a: NSNumber, - b: NSNumber, - c: NSNumber, - d: NSNumber, - e: NSNumber, - f: NSNumber, - resolver resolve: RCTPromiseResolveBlock, - rejecter reject: RCTPromiseRejectBlock) { - do { - let canvasView = try JSContextUtils.unwrapObject(canvasRef, DrawingCanvasView.self) - canvasView.setTransform(a: CGFloat(truncating: a), - b: CGFloat(truncating: b), - c: CGFloat(truncating: c), - d: CGFloat(truncating: d), - e: CGFloat(truncating: e), - f: CGFloat(truncating: f)) - resolve(nil) - } catch { - reject(RCTErrorUnspecified, "Could not perform setTransform: \(error)", error) - } - } - - @objc - func save(_ canvasRef: NSDictionary, + do { + let canvasView = try JSContextUtils.unwrapObject(canvasRef, DrawingCanvasView.self) + canvasView.stroke() + resolve(nil) + } catch { + reject(RCTErrorUnspecified, "Could not perform stroke: \(error)", error) + } + } + + @objc + func fill(_ canvasRef: NSDictionary, + resolver resolve: RCTPromiseResolveBlock, + rejecter reject: RCTPromiseRejectBlock) { + do { + let canvasView = try JSContextUtils.unwrapObject(canvasRef, DrawingCanvasView.self) + canvasView.fill() + resolve(nil) + } catch { + reject(RCTErrorUnspecified, "Could not perform fill: \(error)", error) + } + } + + @objc + func scale(_ canvasRef: NSDictionary, + x: NSNumber, + y: NSNumber, + resolver resolve: RCTPromiseResolveBlock, + rejecter reject: RCTPromiseRejectBlock) { + do { + let canvasView = try JSContextUtils.unwrapObject(canvasRef, DrawingCanvasView.self) + canvasView.scale(x: CGFloat(truncating: x), y: CGFloat(truncating: y)) + resolve(nil) + } catch { + reject(RCTErrorUnspecified, "Could not perform scale: \(error)", error) + } + } + + @objc + func rotate(_ canvasRef: NSDictionary, + angle: NSNumber, resolver resolve: RCTPromiseResolveBlock, rejecter reject: RCTPromiseRejectBlock) { - do { - let canvasView = try JSContextUtils.unwrapObject(canvasRef, DrawingCanvasView.self) - canvasView.save() - resolve(nil) - } catch { - reject(RCTErrorUnspecified, "Could not perform save: \(error)", error) - } - } - - @objc - func restore(_ canvasRef: NSDictionary, + do { + let canvasView = try JSContextUtils.unwrapObject(canvasRef, DrawingCanvasView.self) + canvasView.rotate(angle: CGFloat(truncating: angle)) + resolve(nil) + } catch { + reject(RCTErrorUnspecified, "Could not perform rotate: \(error)", error) + } + } + + @objc + func translate(_ canvasRef: NSDictionary, + x: NSNumber, + y: NSNumber, resolver resolve: RCTPromiseResolveBlock, rejecter reject: RCTPromiseRejectBlock) { - do { - let canvasView = try JSContextUtils.unwrapObject(canvasRef, DrawingCanvasView.self) - canvasView.restore() - resolve(nil) - } catch { - reject(RCTErrorUnspecified, "Could not perform restore: \(error)", error) - } + do { + let canvasView = try JSContextUtils.unwrapObject(canvasRef, DrawingCanvasView.self) + canvasView.translate(x: CGFloat(truncating: x), y: CGFloat(truncating: y)) + resolve(nil) + } catch { + reject(RCTErrorUnspecified, "Could not perform translate: \(error)", error) + } + } + + @objc + func setTransform(_ canvasRef: NSDictionary, + a: NSNumber, + b: NSNumber, + c: NSNumber, + d: NSNumber, + e: NSNumber, + f: NSNumber, + resolver resolve: RCTPromiseResolveBlock, + rejecter reject: RCTPromiseRejectBlock) { + do { + let canvasView = try JSContextUtils.unwrapObject(canvasRef, DrawingCanvasView.self) + canvasView.setTransform(a: CGFloat(truncating: a), + b: CGFloat(truncating: b), + c: CGFloat(truncating: c), + d: CGFloat(truncating: d), + e: CGFloat(truncating: e), + f: CGFloat(truncating: f)) + resolve(nil) + } catch { + reject(RCTErrorUnspecified, "Could not perform setTransform: \(error)", error) + } + } + + @objc + func save(_ canvasRef: NSDictionary, + resolver resolve: RCTPromiseResolveBlock, + rejecter reject: RCTPromiseRejectBlock) { + do { + let canvasView = try JSContextUtils.unwrapObject(canvasRef, DrawingCanvasView.self) + canvasView.save() + resolve(nil) + } catch { + reject(RCTErrorUnspecified, "Could not perform save: \(error)", error) + } + } + + @objc + func restore(_ canvasRef: NSDictionary, + resolver resolve: RCTPromiseResolveBlock, + rejecter reject: RCTPromiseRejectBlock) { + do { + let canvasView = try JSContextUtils.unwrapObject(canvasRef, DrawingCanvasView.self) + canvasView.restore() + resolve(nil) + } catch { + reject(RCTErrorUnspecified, "Could not perform restore: \(error)", error) + } + } + + @objc + func setFillStyle(_ canvasRef: NSDictionary, + color: CGColor, + resolver resolve: RCTPromiseResolveBlock, + rejecter reject: RCTPromiseRejectBlock) { + do { + let canvasView = try JSContextUtils.unwrapObject(canvasRef, DrawingCanvasView.self) + canvasView.setFillStyle(color: color) + resolve(nil) + } catch { + reject(RCTErrorUnspecified, "Could not perform setFillStyle: \(error)", error) } + } - @objc - func setFillStyle(_ canvasRef: NSDictionary, + @objc + func setStrokeStyle(_ canvasRef: NSDictionary, color: CGColor, resolver resolve: RCTPromiseResolveBlock, rejecter reject: RCTPromiseRejectBlock) { - do { - let canvasView = try JSContextUtils.unwrapObject(canvasRef, DrawingCanvasView.self) - canvasView.setFillStyle(color: color) - resolve(nil) - } catch { - reject(RCTErrorUnspecified, "Could not perform setFillStyle: \(error)", error) - } - } - - @objc - func setStrokeStyle(_ canvasRef: NSDictionary, - color: CGColor, - resolver resolve: RCTPromiseResolveBlock, - rejecter reject: RCTPromiseRejectBlock) { - do { - let canvasView = try JSContextUtils.unwrapObject(canvasRef, DrawingCanvasView.self) - canvasView.setStrokeStyle(color: color) - resolve(nil) - } catch { - reject(RCTErrorUnspecified, "Could not perform setStrokeStyle: \(error)", error) - } - } - - @objc - func setLineWidth(_ canvasRef: NSDictionary, - lineWidth: NSNumber, - resolver resolve: RCTPromiseResolveBlock, - rejecter reject: RCTPromiseRejectBlock) { - do { - let canvasView = try JSContextUtils.unwrapObject(canvasRef, DrawingCanvasView.self) - canvasView.setLineWidth(lineWidth: CGFloat(truncating: lineWidth)) - resolve(nil) - } catch { - reject(RCTErrorUnspecified, "Could not perform setLineWidth: \(error)", error) - } - } - - @objc - func setLineCap(_ canvasRef: NSDictionary, - lineCap: NSString, + do { + let canvasView = try JSContextUtils.unwrapObject(canvasRef, DrawingCanvasView.self) + canvasView.setStrokeStyle(color: color) + resolve(nil) + } catch { + reject(RCTErrorUnspecified, "Could not perform setStrokeStyle: \(error)", error) + } + } + + @objc + func setLineWidth(_ canvasRef: NSDictionary, + lineWidth: NSNumber, resolver resolve: RCTPromiseResolveBlock, rejecter reject: RCTPromiseRejectBlock) { - do { - let canvasView = try JSContextUtils.unwrapObject(canvasRef, DrawingCanvasView.self) - try canvasView.setLineCap(lineCap: lineCap as String) - resolve(nil) - } catch { - reject(RCTErrorUnspecified, "Could not perform setLineCap: \(error)", error) - } - } - - @objc - func setLineJoin(_ canvasRef: NSDictionary, - lineJoin: NSString, + do { + let canvasView = try JSContextUtils.unwrapObject(canvasRef, DrawingCanvasView.self) + canvasView.setLineWidth(lineWidth: CGFloat(truncating: lineWidth)) + resolve(nil) + } catch { + reject(RCTErrorUnspecified, "Could not perform setLineWidth: \(error)", error) + } + } + + @objc + func setLineCap(_ canvasRef: NSDictionary, + lineCap: NSString, + resolver resolve: RCTPromiseResolveBlock, + rejecter reject: RCTPromiseRejectBlock) { + do { + let canvasView = try JSContextUtils.unwrapObject(canvasRef, DrawingCanvasView.self) + try canvasView.setLineCap(lineCap: lineCap as String) + resolve(nil) + } catch { + reject(RCTErrorUnspecified, "Could not perform setLineCap: \(error)", error) + } + } + + @objc + func setLineJoin(_ canvasRef: NSDictionary, + lineJoin: NSString, + resolver resolve: RCTPromiseResolveBlock, + rejecter reject: RCTPromiseRejectBlock) { + do { + let canvasView = try JSContextUtils.unwrapObject(canvasRef, DrawingCanvasView.self) + try canvasView.setLineJoin(lineJoin: lineJoin as String) + resolve(nil) + } catch { + reject(RCTErrorUnspecified, "Could not perform setLineJoin: \(error)", error) + } + } + + @objc + func setMiterLimit(_ canvasRef: NSDictionary, + miterLimit: NSNumber, resolver resolve: RCTPromiseResolveBlock, rejecter reject: RCTPromiseRejectBlock) { - do { - let canvasView = try JSContextUtils.unwrapObject(canvasRef, DrawingCanvasView.self) - try canvasView.setLineJoin(lineJoin: lineJoin as String) - resolve(nil) - } catch { - reject(RCTErrorUnspecified, "Could not perform setLineJoin: \(error)", error) - } - } - - @objc - func setMiterLimit(_ canvasRef: NSDictionary, - miterLimit: NSNumber, - resolver resolve: RCTPromiseResolveBlock, - rejecter reject: RCTPromiseRejectBlock) { - do { - let canvasView = try JSContextUtils.unwrapObject(canvasRef, DrawingCanvasView.self) - canvasView.setMiterLimit(miterLimit: CGFloat(truncating: miterLimit)) - resolve(nil) - } catch { - reject(RCTErrorUnspecified, "Could not perform setMiterLimit: \(error)", error) - } - } - - @objc - func setFont(_ canvasRef: NSDictionary, - font: NSDictionary, + do { + let canvasView = try JSContextUtils.unwrapObject(canvasRef, DrawingCanvasView.self) + canvasView.setMiterLimit(miterLimit: CGFloat(truncating: miterLimit)) + resolve(nil) + } catch { + reject(RCTErrorUnspecified, "Could not perform setMiterLimit: \(error)", error) + } + } + + @objc + func setFont(_ canvasRef: NSDictionary, + font: NSDictionary, + resolver resolve: RCTPromiseResolveBlock, + rejecter reject: RCTPromiseRejectBlock) { + do { + let canvasView = try JSContextUtils.unwrapObject(canvasRef, DrawingCanvasView.self) + try canvasView.setFont(font: font) + resolve(nil) + } catch { + reject(RCTErrorUnspecified, "Could not perform setFont: \(error)", error) + } + } + + @objc + func beginPath(_ canvasRef: NSDictionary, resolver resolve: RCTPromiseResolveBlock, rejecter reject: RCTPromiseRejectBlock) { - do { - let canvasView = try JSContextUtils.unwrapObject(canvasRef, DrawingCanvasView.self) - try canvasView.setFont(font: font) - resolve(nil) - } catch { - reject(RCTErrorUnspecified, "Could not perform setFont: \(error)", error) - } + do { + let canvasView = try JSContextUtils.unwrapObject(canvasRef, DrawingCanvasView.self) + canvasView.beginPath() + resolve(nil) + } catch { + reject(RCTErrorUnspecified, "Could not perform beginPath: \(error)", error) } + } - @objc - func beginPath(_ canvasRef: NSDictionary, - resolver resolve: RCTPromiseResolveBlock, - rejecter reject: RCTPromiseRejectBlock) { - do { - let canvasView = try JSContextUtils.unwrapObject(canvasRef, DrawingCanvasView.self) - canvasView.beginPath() - resolve(nil) - } catch { - reject(RCTErrorUnspecified, "Could not perform beginPath: \(error)", error) - } + @objc + func closePath(_ canvasRef: NSDictionary, + resolver resolve: RCTPromiseResolveBlock, + rejecter reject: RCTPromiseRejectBlock) { + do { + let canvasView = try JSContextUtils.unwrapObject(canvasRef, DrawingCanvasView.self) + canvasView.closePath() + resolve(nil) + } catch { + reject(RCTErrorUnspecified, "Could not perform closePath: \(error)", error) } + } - @objc - func closePath(_ canvasRef: NSDictionary, - resolver resolve: RCTPromiseResolveBlock, - rejecter reject: RCTPromiseRejectBlock) { - do { - let canvasView = try JSContextUtils.unwrapObject(canvasRef, DrawingCanvasView.self) - canvasView.closePath() - resolve(nil) - } catch { - reject(RCTErrorUnspecified, "Could not perform closePath: \(error)", error) - } + @objc + func lineTo(_ canvasRef: NSDictionary, + x: NSNumber, + y: NSNumber, + resolver resolve: RCTPromiseResolveBlock, + rejecter reject: RCTPromiseRejectBlock) { + do { + let canvasView = try JSContextUtils.unwrapObject(canvasRef, DrawingCanvasView.self) + let point = CGPoint(x: CGFloat(truncating: x), y: CGFloat(truncating: y)) + canvasView.lineTo(point: point) + resolve(nil) + } catch { + reject(RCTErrorUnspecified, "Could not perform lineTo: \(error)", error) + } + } + + @objc + func moveTo(_ canvasRef: NSDictionary, + x: NSNumber, + y: NSNumber, + resolver resolve: RCTPromiseResolveBlock, + rejecter reject: RCTPromiseRejectBlock) { + do { + let canvasView = try JSContextUtils.unwrapObject(canvasRef, DrawingCanvasView.self) + let point = CGPoint(x: CGFloat(truncating: x), y: CGFloat(truncating: y)) + canvasView.moveTo(point: point) + resolve(nil) + } catch { + reject(RCTErrorUnspecified, "Could not perform moveTo: \(error)", error) + } + } + + @objc + func drawCircle(_ canvasRef: NSDictionary, + x: NSNumber, + y: NSNumber, + radius: NSNumber, + resolver resolve: RCTPromiseResolveBlock, + rejecter reject: RCTPromiseRejectBlock) { + do { + let canvasView = try JSContextUtils.unwrapObject(canvasRef, DrawingCanvasView.self) + canvasView.drawCircle(x: CGFloat(truncating: x), + y: CGFloat(truncating: y), + radius: CGFloat(truncating: radius)) + resolve(nil) + } catch { + reject(RCTErrorUnspecified, "Could not perform drawCircle: \(error)", error) } + } - @objc - func lineTo(_ canvasRef: NSDictionary, - x: NSNumber, - y: NSNumber, - resolver resolve: RCTPromiseResolveBlock, - rejecter reject: RCTPromiseRejectBlock) { - do { - let canvasView = try JSContextUtils.unwrapObject(canvasRef, DrawingCanvasView.self) - let point = CGPoint(x: CGFloat(truncating: x), y: CGFloat(truncating: y)) - canvasView.lineTo(point: point) - resolve(nil) - } catch { - reject(RCTErrorUnspecified, "Could not perform lineTo: \(error)", error) - } - } - - @objc - func moveTo(_ canvasRef: NSDictionary, + @objc + func fillCircle(_ canvasRef: NSDictionary, + x: NSNumber, + y: NSNumber, + radius: NSNumber, + resolver resolve: RCTPromiseResolveBlock, + rejecter reject: RCTPromiseRejectBlock) { + do { + let canvasView = try JSContextUtils.unwrapObject(canvasRef, DrawingCanvasView.self) + canvasView.drawCircle(x: CGFloat(truncating: x), + y: CGFloat(truncating: y), + radius: CGFloat(truncating: radius), + fill: true) + resolve(nil) + } catch { + reject(RCTErrorUnspecified, "Could not perform fillCircle: \(error)", error) + } + } + + @objc + func fillText(_ canvasRef: NSDictionary, + text: NSString, x: NSNumber, y: NSNumber, resolver resolve: RCTPromiseResolveBlock, rejecter reject: RCTPromiseRejectBlock) { - do { - let canvasView = try JSContextUtils.unwrapObject(canvasRef, DrawingCanvasView.self) - let point = CGPoint(x: CGFloat(truncating: x), y: CGFloat(truncating: y)) - canvasView.moveTo(point: point) - resolve(nil) - } catch { - reject(RCTErrorUnspecified, "Could not perform moveTo: \(error)", error) - } - } - - @objc - func drawCircle(_ canvasRef: NSDictionary, - x: NSNumber, - y: NSNumber, - radius: NSNumber, - resolver resolve: RCTPromiseResolveBlock, - rejecter reject: RCTPromiseRejectBlock) { - do { - let canvasView = try JSContextUtils.unwrapObject(canvasRef, DrawingCanvasView.self) - canvasView.drawCircle(x: CGFloat(truncating: x), - y: CGFloat(truncating: y), - radius: CGFloat(truncating: radius)) - resolve(nil) - } catch { - reject(RCTErrorUnspecified, "Could not perform drawCircle: \(error)", error) - } - } - - @objc - func fillCircle(_ canvasRef: NSDictionary, - x: NSNumber, - y: NSNumber, - radius: NSNumber, - resolver resolve: RCTPromiseResolveBlock, - rejecter reject: RCTPromiseRejectBlock) { - do { - let canvasView = try JSContextUtils.unwrapObject(canvasRef, DrawingCanvasView.self) - canvasView.drawCircle(x: CGFloat(truncating: x), - y: CGFloat(truncating: y), - radius: CGFloat(truncating: radius), - fill: true) - resolve(nil) - } catch { - reject(RCTErrorUnspecified, "Could not perform fillCircle: \(error)", error) - } - } - - @objc - func fillText(_ canvasRef: NSDictionary, + do { + let canvasView = try JSContextUtils.unwrapObject(canvasRef, DrawingCanvasView.self) + canvasView.fillText(text: text as String, x: CGFloat(truncating: x), y: CGFloat(truncating: y)) + resolve(nil) + } catch { + reject(RCTErrorUnspecified, "Could not perform fillText: \(error)", error) + } + } + + @objc + func strokeText(_ canvasRef: NSDictionary, text: NSString, x: NSNumber, y: NSNumber, resolver resolve: RCTPromiseResolveBlock, rejecter reject: RCTPromiseRejectBlock) { - do { - let canvasView = try JSContextUtils.unwrapObject(canvasRef, DrawingCanvasView.self) - canvasView.fillText(text: text as String, x: CGFloat(truncating: x), y: CGFloat(truncating: y)) - resolve(nil) - } catch { - reject(RCTErrorUnspecified, "Could not perform fillText: \(error)", error) - } - } - - @objc - func strokeText(_ canvasRef: NSDictionary, - text: NSString, - x: NSNumber, - y: NSNumber, + do { + let canvasView = try JSContextUtils.unwrapObject(canvasRef, DrawingCanvasView.self) + canvasView.fillText(text: text as String, x: CGFloat(truncating: x), y: CGFloat(truncating: y), fill: false) + resolve(nil) + } catch { + reject(RCTErrorUnspecified, "Could not perform strokeText: \(error)", error) + } + } + + @objc + func setTextAlign(_ canvasRef: NSDictionary, + textAlign: NSString, resolver resolve: RCTPromiseResolveBlock, rejecter reject: RCTPromiseRejectBlock) { - do { - let canvasView = try JSContextUtils.unwrapObject(canvasRef, DrawingCanvasView.self) - canvasView.fillText(text: text as String, x: CGFloat(truncating: x), y: CGFloat(truncating: y), fill: false) - resolve(nil) - } catch { - reject(RCTErrorUnspecified, "Could not perform strokeText: \(error)", error) - } - } - - @objc - func setTextAlign(_ canvasRef: NSDictionary, - textAlign: NSString, - resolver resolve: RCTPromiseResolveBlock, - rejecter reject: RCTPromiseRejectBlock) { - do { - let canvasView = try JSContextUtils.unwrapObject(canvasRef, DrawingCanvasView.self) - canvasView.setTextAlign(textAlign: textAlign as String) - resolve(nil) - } catch { - reject(RCTErrorUnspecified, "Could not perform setTextAlign: \(error)", error) - } - } - - @objc - func drawImage(_ canvasRef: NSDictionary, - image: NSDictionary, - sx: NSNumber, - sy: NSNumber, - sWidth: NSNumber, - sHeight: NSNumber, - dx: NSNumber, - dy: NSNumber, - dWidth: NSNumber, - dHeight: NSNumber, - resolver resolve: RCTPromiseResolveBlock, - rejecter reject: RCTPromiseRejectBlock) { - do { - let canvasView = try JSContextUtils.unwrapObject(canvasRef, DrawingCanvasView.self) - let image = try JSContextUtils.unwrapObject(image, IImage.self) - if dWidth == -1 && sWidth == -1 { - try canvasView.drawImage(image: image, dx: CGFloat(truncating: sx), dy: CGFloat(truncating: sy)) - } else if dx == -1 { - try canvasView.drawImage(image: image, - dx: CGFloat(truncating: sx), - dy: CGFloat(truncating: sy), - dWidth: CGFloat(truncating: sWidth), - dHeight: CGFloat(truncating: sHeight)) - } else { - try canvasView.drawImage(image: image, - sx: CGFloat(truncating: sx), - sy: CGFloat(truncating: sy), - sWidth: CGFloat(truncating: sWidth), - sHeight: CGFloat(truncating: sHeight), - dx: CGFloat(truncating: dx), - dy: CGFloat(truncating: dy), - dWidth: CGFloat(truncating: dWidth), - dHeight: CGFloat(truncating: dHeight)) - } - resolve(nil) - } catch { - reject(RCTErrorUnspecified, "Could not perform drawImage: \(error)", error) - } - } - - @objc - func getImageData(_ canvasRef: NSDictionary, - sx: NSNumber, - sy: NSNumber, - sw: NSNumber, - sh: NSNumber, - resolver resolve: RCTPromiseResolveBlock, - rejecter reject: RCTPromiseRejectBlock) { - do { - let canvasView = try JSContextUtils.unwrapObject(canvasRef, DrawingCanvasView.self) - let completionHandler: (ImageData?) -> Void = { imageData in - let ref = JSContext.wrapObject(object: imageData!).getJSRef() - resolve(ref) - } - try canvasView.getImageData(sx: CGFloat(truncating: sx), - sy: CGFloat(truncating: sy), - sw: CGFloat(truncating: sw), - sh: CGFloat(truncating: sh), - completionHandler: completionHandler) - } catch { - reject(RCTErrorUnspecified, "Could not perform getImageData: \(error)", error) - } - } - - @objc - func putImageData(_ canvasRef: NSDictionary, - imageDataRef: NSDictionary, - sx: NSNumber, - sy: NSNumber, - resolver resolve: RCTPromiseResolveBlock, - rejecter reject: RCTPromiseRejectBlock) { - do { - let canvasView = try JSContextUtils.unwrapObject(canvasRef, DrawingCanvasView.self) - let imageData = try JSContextUtils.unwrapObject(imageDataRef, ImageData.self) - try canvasView.putImageData(imageData: imageData, sx: CGFloat(truncating: sx), sy: CGFloat(truncating: sy)) - resolve(nil) - } catch { - reject(RCTErrorUnspecified, "Could not perform putImageData: \(error)", error) - } - } + do { + let canvasView = try JSContextUtils.unwrapObject(canvasRef, DrawingCanvasView.self) + canvasView.setTextAlign(textAlign: textAlign as String) + resolve(nil) + } catch { + reject(RCTErrorUnspecified, "Could not perform setTextAlign: \(error)", error) + } + } + + @objc + func drawImage(_ canvasRef: NSDictionary, + image: NSDictionary, + sx: NSNumber, + sy: NSNumber, + sWidth: NSNumber, + sHeight: NSNumber, + dx: NSNumber, + dy: NSNumber, + dWidth: NSNumber, + dHeight: NSNumber, + resolver resolve: RCTPromiseResolveBlock, + rejecter reject: RCTPromiseRejectBlock) { + do { + let canvasView = try JSContextUtils.unwrapObject(canvasRef, DrawingCanvasView.self) + let image = try JSContextUtils.unwrapObject(image, IImage.self) + if dWidth == -1 && sWidth == -1 { + try canvasView.drawImage(image: image, dx: CGFloat(truncating: sx), dy: CGFloat(truncating: sy)) + } else if dx == -1 { + try canvasView.drawImage(image: image, + dx: CGFloat(truncating: sx), + dy: CGFloat(truncating: sy), + dWidth: CGFloat(truncating: sWidth), + dHeight: CGFloat(truncating: sHeight)) + } else { + try canvasView.drawImage(image: image, + sx: CGFloat(truncating: sx), + sy: CGFloat(truncating: sy), + sWidth: CGFloat(truncating: sWidth), + sHeight: CGFloat(truncating: sHeight), + dx: CGFloat(truncating: dx), + dy: CGFloat(truncating: dy), + dWidth: CGFloat(truncating: dWidth), + dHeight: CGFloat(truncating: dHeight)) + } + resolve(nil) + } catch { + reject(RCTErrorUnspecified, "Could not perform drawImage: \(error)", error) + } + } + + @objc + func getImageData(_ canvasRef: NSDictionary, + sx: NSNumber, + sy: NSNumber, + sw: NSNumber, + sh: NSNumber, + resolver resolve: RCTPromiseResolveBlock, + rejecter reject: RCTPromiseRejectBlock) { + do { + let canvasView = try JSContextUtils.unwrapObject(canvasRef, DrawingCanvasView.self) + let completionHandler: (ImageData?) -> Void = { imageData in + let ref = JSContext.wrapObject(object: imageData!).getJSRef() + resolve(ref) + } + try canvasView.getImageData(sx: CGFloat(truncating: sx), + sy: CGFloat(truncating: sy), + sw: CGFloat(truncating: sw), + sh: CGFloat(truncating: sh), + completionHandler: completionHandler) + } catch { + reject(RCTErrorUnspecified, "Could not perform getImageData: \(error)", error) + } + } + + @objc + func putImageData(_ canvasRef: NSDictionary, + imageDataRef: NSDictionary, + sx: NSNumber, + sy: NSNumber, + resolver resolve: RCTPromiseResolveBlock, + rejecter reject: RCTPromiseRejectBlock) { + do { + let canvasView = try JSContextUtils.unwrapObject(canvasRef, DrawingCanvasView.self) + let imageData = try JSContextUtils.unwrapObject(imageDataRef, ImageData.self) + try canvasView.putImageData(imageData: imageData, sx: CGFloat(truncating: sx), sy: CGFloat(truncating: sy)) + resolve(nil) + } catch { + reject(RCTErrorUnspecified, "Could not perform putImageData: \(error)", error) + } + } } diff --git a/react-native-pytorch-core/ios/Canvas/DrawingCanvasView.swift b/react-native-pytorch-core/ios/Canvas/DrawingCanvasView.swift index 821bad9bd..c03f0997b 100644 --- a/react-native-pytorch-core/ios/Canvas/DrawingCanvasView.swift +++ b/react-native-pytorch-core/ios/Canvas/DrawingCanvasView.swift @@ -11,531 +11,531 @@ import UIKit @objc(DrawingCanvasView) class DrawingCanvasView: UIView { - enum CanvasError: Error { - case invalidLineJoinValue - case invalidLineCapValue - case invalidFontFamily - case unableToCreateBitmap - } - - @objc public var onContext2D: RCTBubblingEventBlock? - var ref: [String: String] = [:] // initialized to allow using self in init() - var stateStack = Stack() - var path = CGMutablePath() - var currentState = CanvasState() - var sublayers = [LayerData]() - let scaleText = UIScreen.main.scale - var shapeLayers = [ShapeLayerData]() - var needsClear = false - - override public init(frame: CGRect) { - super.init(frame: frame) - self.clipsToBounds = true - self.autoresizingMask = [.flexibleHeight, .flexibleWidth] - ref = JSContext.wrapObject(object: self).getJSRef() - } - - required init?(coder aDecoder: NSCoder) { - super.init(coder: aDecoder) - self.clipsToBounds = true - self.autoresizingMask = [.flexibleHeight, .flexibleWidth] - ref = JSContext.wrapObject(object: self).getJSRef() - } - - override class var layerClass: AnyClass { - return CATransformLayer.self - } - - override func didSetProps(_ changedProps: [String]!) { - guard let unwrappedOnContext2D = onContext2D else { return } - unwrappedOnContext2D(["ID": ref[JSContext.idKey] as Any]) - } - - func arc(x: CGFloat, y: CGFloat, radius: CGFloat, startAngle: CGFloat, endAngle: CGFloat, counterclockwise: Bool) { - // seems counterintuitve to set clockwise to counterclockwise, but is the only way to get it to match web canvas - path.addArc(center: CGPoint(x: x, y: y), - radius: radius, - startAngle: startAngle, - endAngle: endAngle, - clockwise: counterclockwise) - } - - func strokeRect(x: CGFloat, y: CGFloat, width: CGFloat, height: CGFloat) { - let rect = CGRect(x: x, y: y, width: width, height: height) - let p = CGMutablePath() - p.addRect(rect) - var state = CanvasState(state: currentState) - state.fillStyle = UIColor.clear.cgColor - let newLayer = ShapeLayerData(path: p, state: state) - sublayers.append(newLayer) - } - - func fillRect(x: CGFloat, y: CGFloat, width: CGFloat, height: CGFloat) { - let rect = CGRect(x: x, y: y, width: width, height: height) - let p = CGMutablePath() - p.addRect(rect) - var state = CanvasState(state: currentState) - state.lineWidth = 0 - let newLayer = ShapeLayerData(path: p, state: state) - sublayers.append(newLayer) - } - - func rect(x: CGFloat, y: CGFloat, width: CGFloat, height: CGFloat) { - // create CGRect and add it to list - let rect = CGRect(x: x, y: y, width: width, height: height) - path.addRect(rect) - } - - private func renderLayers(allLayerData: [LayerData], layer: CALayer) { - for layerData in allLayerData { - let baseLayer = CALayer() - baseLayer.transform = layerData.transform - switch layerData.type { - case .shapeLayer: - guard let data = layerData as? ShapeLayerData else { - continue - } - let newLayer = CAShapeLayer() - newLayer.setStyle(state: data.state) - baseLayer.transform = data.state.transform - newLayer.path = data.path - baseLayer.addSublayer(newLayer) - case .textLayer: - guard let data = layerData as? TextLayerData else { - continue - } - let newLayer = CATextLayer() - newLayer.frame = data.frame - newLayer.string = data.text - newLayer.contentsScale = self.scaleText - baseLayer.addSublayer(newLayer) - case .imageLayer: - guard let data = layerData as? ImageLayerData else { - continue - } - let newLayer = CALayer() - newLayer.contents = data.image - newLayer.frame = data.frame - baseLayer.addSublayer(newLayer) - } - layer.addSublayer(baseLayer) - } - } - - func invalidate() { - let sublayers = self.sublayers - self.sublayers.removeAll() - let needClear = self.needsClear - self.needsClear = false - - DispatchQueue.main.async { - if needClear { - self.layer.sublayers?.removeAll() - } - self.renderLayers(allLayerData: sublayers, layer: self.layer) - self.layer.layoutIfNeeded() - } - } - - func clear() { - sublayers.removeAll() - needsClear = true - } - - func clearRect(x: CGFloat, y: CGFloat, width: CGFloat, height: CGFloat) { // doesn't work yet - let tempColor = currentState.fillStyle - currentState.fillStyle = UIColor.white.cgColor - fillRect(x: x, y: y, width: width, height: height) - currentState.fillStyle = tempColor - } - - func stroke() { - if !path.isEmpty { - onTransformationChange() - } - var state = CanvasState(state: currentState) - state.fillStyle = UIColor.clear.cgColor - for sld in shapeLayers { - let newLayer = ShapeLayerData(path: sld.path, state: state) - sublayers.append(newLayer) - } - } - - func fill() { - if !path.isEmpty { - onTransformationChange() - } - var state = CanvasState(state: currentState) - state.lineWidth = 0 - for sld in shapeLayers { - let newLayer = ShapeLayerData(path: sld.path, state: state) - sublayers.append(newLayer) - } - } - - func scale(x: CGFloat, y: CGFloat) { - if !path.isEmpty { - onTransformationChange() - } - currentState.transform = CATransform3DScale(currentState.transform, x, y, 1.0) - } - - func rotate(angle: CGFloat) { - if !path.isEmpty { - onTransformationChange() - } - // If the vector has zero length, the behavior is undefined: t = rotation(angle, x, y, z) * t. - currentState.transform = CATransform3DRotate(currentState.transform, angle, 0.0, 0.0, 1.0) - } - - func translate(x: CGFloat, y: CGFloat) { - if !path.isEmpty { - onTransformationChange() - } - currentState.transform = CATransform3DTranslate(currentState.transform, x, y, 0.0) - } - - func setTransform(a: CGFloat, b: CGFloat, c: CGFloat, d: CGFloat, e: CGFloat, f: CGFloat) { - if !path.isEmpty { - onTransformationChange() - } - // Note that the Apple CGAffineTransform matrix is the transpose of the - // matrix used by PyTorch Live, but so is their labeling - currentState.transform = CATransform3DMakeAffineTransform(CGAffineTransform(a: a, - b: b, - c: c, - d: d, - tx: e, - ty: f)) - } - - func setFillStyle(color: CGColor) { - currentState.fillStyle = color - } - - func setStrokeStyle(color: CGColor) { - currentState.strokeStyle = color - } - - func save() { - stateStack.push(state: currentState) - currentState = CanvasState(state: currentState) - } - - func restore() { - if let s = stateStack.pop() { - currentState = s - } - } - - func setLineWidth(lineWidth: CGFloat) { - currentState.lineWidth = lineWidth - } - - func setLineCap(lineCap: String) throws { - switch lineCap { - case "butt": - currentState.lineCap = CAShapeLayerLineCap.butt - case "round": - currentState.lineCap = CAShapeLayerLineCap.round - case "square": - currentState.lineCap = CAShapeLayerLineCap.square - default: - throw CanvasError.invalidLineCapValue - } - } - - func setLineJoin(lineJoin: String) throws { - switch lineJoin { - case "miter": - currentState.lineJoin = CAShapeLayerLineJoin.miter - case "round": - currentState.lineJoin = CAShapeLayerLineJoin.round - case "bevel": - currentState.lineJoin = CAShapeLayerLineJoin.bevel - default: - throw CanvasError.invalidLineJoinValue - } - } - - func setMiterLimit(miterLimit: CGFloat) { - currentState.miterLimit = miterLimit - } - - // swiftlint:disable cyclomatic_complexity - func setFont(font: NSDictionary) throws { - if let fr = currentState.fontRepresentation, fr.isEqual(font) { - return + enum CanvasError: Error { + case invalidLineJoinValue + case invalidLineCapValue + case invalidFontFamily + case unableToCreateBitmap + } + + @objc public var onContext2D: RCTBubblingEventBlock? + var ref: [String: String] = [:] // initialized to allow using self in init() + var stateStack = Stack() + var path = CGMutablePath() + var currentState = CanvasState() + var sublayers = [LayerData]() + let scaleText = UIScreen.main.scale + var shapeLayers = [ShapeLayerData]() + var needsClear = false + + override public init(frame: CGRect) { + super.init(frame: frame) + self.clipsToBounds = true + self.autoresizingMask = [.flexibleHeight, .flexibleWidth] + ref = JSContext.wrapObject(object: self).getJSRef() + } + + required init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + self.clipsToBounds = true + self.autoresizingMask = [.flexibleHeight, .flexibleWidth] + ref = JSContext.wrapObject(object: self).getJSRef() + } + + override class var layerClass: AnyClass { + return CATransformLayer.self + } + + override func didSetProps(_ changedProps: [String]!) { + guard let unwrappedOnContext2D = onContext2D else { return } + unwrappedOnContext2D(["ID": ref[JSContext.idKey] as Any]) + } + + func arc(x: CGFloat, y: CGFloat, radius: CGFloat, startAngle: CGFloat, endAngle: CGFloat, counterclockwise: Bool) { + // seems counterintuitve to set clockwise to counterclockwise, but is the only way to get it to match web canvas + path.addArc(center: CGPoint(x: x, y: y), + radius: radius, + startAngle: startAngle, + endAngle: endAngle, + clockwise: counterclockwise) + } + + func strokeRect(x: CGFloat, y: CGFloat, width: CGFloat, height: CGFloat) { + let rect = CGRect(x: x, y: y, width: width, height: height) + let p = CGMutablePath() + p.addRect(rect) + var state = CanvasState(state: currentState) + state.fillStyle = UIColor.clear.cgColor + let newLayer = ShapeLayerData(path: p, state: state) + sublayers.append(newLayer) + } + + func fillRect(x: CGFloat, y: CGFloat, width: CGFloat, height: CGFloat) { + let rect = CGRect(x: x, y: y, width: width, height: height) + let p = CGMutablePath() + p.addRect(rect) + var state = CanvasState(state: currentState) + state.lineWidth = 0 + let newLayer = ShapeLayerData(path: p, state: state) + sublayers.append(newLayer) + } + + func rect(x: CGFloat, y: CGFloat, width: CGFloat, height: CGFloat) { + // create CGRect and add it to list + let rect = CGRect(x: x, y: y, width: width, height: height) + path.addRect(rect) + } + + private func renderLayers(allLayerData: [LayerData], layer: CALayer) { + for layerData in allLayerData { + let baseLayer = CALayer() + baseLayer.transform = layerData.transform + switch layerData.type { + case .shapeLayer: + guard let data = layerData as? ShapeLayerData else { + continue + } + let newLayer = CAShapeLayer() + newLayer.setStyle(state: data.state) + baseLayer.transform = data.state.transform + newLayer.path = data.path + baseLayer.addSublayer(newLayer) + case .textLayer: + guard let data = layerData as? TextLayerData else { + continue + } + let newLayer = CATextLayer() + newLayer.frame = data.frame + newLayer.string = data.text + newLayer.contentsScale = self.scaleText + baseLayer.addSublayer(newLayer) + case .imageLayer: + guard let data = layerData as? ImageLayerData else { + continue + } + let newLayer = CALayer() + newLayer.contents = data.image + newLayer.frame = data.frame + baseLayer.addSublayer(newLayer) + } + layer.addSublayer(baseLayer) + } + } + + func invalidate() { + let sublayers = self.sublayers + self.sublayers.removeAll() + let needClear = self.needsClear + self.needsClear = false + + DispatchQueue.main.async { + if needClear { + self.layer.sublayers?.removeAll() + } + self.renderLayers(allLayerData: sublayers, layer: self.layer) + self.layer.layoutIfNeeded() + } + } + + func clear() { + sublayers.removeAll() + needsClear = true + } + + func clearRect(x: CGFloat, y: CGFloat, width: CGFloat, height: CGFloat) { // doesn't work yet + let tempColor = currentState.fillStyle + currentState.fillStyle = UIColor.white.cgColor + fillRect(x: x, y: y, width: width, height: height) + currentState.fillStyle = tempColor + } + + func stroke() { + if !path.isEmpty { + onTransformationChange() + } + var state = CanvasState(state: currentState) + state.fillStyle = UIColor.clear.cgColor + for sld in shapeLayers { + let newLayer = ShapeLayerData(path: sld.path, state: state) + sublayers.append(newLayer) + } + } + + func fill() { + if !path.isEmpty { + onTransformationChange() + } + var state = CanvasState(state: currentState) + state.lineWidth = 0 + for sld in shapeLayers { + let newLayer = ShapeLayerData(path: sld.path, state: state) + sublayers.append(newLayer) + } + } + + func scale(x: CGFloat, y: CGFloat) { + if !path.isEmpty { + onTransformationChange() + } + currentState.transform = CATransform3DScale(currentState.transform, x, y, 1.0) + } + + func rotate(angle: CGFloat) { + if !path.isEmpty { + onTransformationChange() + } + // If the vector has zero length, the behavior is undefined: t = rotation(angle, x, y, z) * t. + currentState.transform = CATransform3DRotate(currentState.transform, angle, 0.0, 0.0, 1.0) + } + + func translate(x: CGFloat, y: CGFloat) { + if !path.isEmpty { + onTransformationChange() + } + currentState.transform = CATransform3DTranslate(currentState.transform, x, y, 0.0) + } + + func setTransform(a: CGFloat, b: CGFloat, c: CGFloat, d: CGFloat, e: CGFloat, f: CGFloat) { + if !path.isEmpty { + onTransformationChange() + } + // Note that the Apple CGAffineTransform matrix is the transpose of the + // matrix used by PyTorch Live, but so is their labeling + currentState.transform = CATransform3DMakeAffineTransform(CGAffineTransform(a: a, + b: b, + c: c, + d: d, + tx: e, + ty: f)) + } + + func setFillStyle(color: CGColor) { + currentState.fillStyle = color + } + + func setStrokeStyle(color: CGColor) { + currentState.strokeStyle = color + } + + func save() { + stateStack.push(state: currentState) + currentState = CanvasState(state: currentState) + } + + func restore() { + if let s = stateStack.pop() { + currentState = s + } + } + + func setLineWidth(lineWidth: CGFloat) { + currentState.lineWidth = lineWidth + } + + func setLineCap(lineCap: String) throws { + switch lineCap { + case "butt": + currentState.lineCap = CAShapeLayerLineCap.butt + case "round": + currentState.lineCap = CAShapeLayerLineCap.round + case "square": + currentState.lineCap = CAShapeLayerLineCap.square + default: + throw CanvasError.invalidLineCapValue + } + } + + func setLineJoin(lineJoin: String) throws { + switch lineJoin { + case "miter": + currentState.lineJoin = CAShapeLayerLineJoin.miter + case "round": + currentState.lineJoin = CAShapeLayerLineJoin.round + case "bevel": + currentState.lineJoin = CAShapeLayerLineJoin.bevel + default: + throw CanvasError.invalidLineJoinValue + } + } + + func setMiterLimit(miterLimit: CGFloat) { + currentState.miterLimit = miterLimit + } + + // swiftlint:disable cyclomatic_complexity + func setFont(font: NSDictionary) throws { + if let fr = currentState.fontRepresentation, fr.isEqual(font) { + return + } else { + currentState.fontRepresentation = font + } + + var fontName = "" + var serif = false + + if let fontFamilyArr = font["fontFamily"] as? NSArray, let fontFamily = fontFamilyArr[0] as? String { + switch fontFamily { + case "serif": + fontName = "TimesNewRomanPS-" + serif = true + case "sans-serif": + fontName = "HelveticaNeue-" + case "monospace": + fontName = "Menlo-" + default: + throw CanvasError.invalidFontFamily + } + } + + if let fontWeight = font["fontWeight"] as? NSString { + if fontWeight == "bold" { + fontName += "Bold" + } + } + + if let fontStyle = font["fontStyle"] as? NSString { + if fontStyle == "italic" { + fontName += "Italic" + } + } + + if fontName.last == "-" { + fontName.removeLast() + } + + if serif { + fontName += "MT" + } + + currentState.font = UIFont(name: fontName, size: 10.0) ?? .systemFont(ofSize: 10) + + if let fontSizeString = font["fontSize"] as? NSString { + let fontSize = CGFloat(fontSizeString.floatValue) + currentState.font = currentState.font.withSize(fontSize) + } + } + + func setTextAlign(textAlign: String) { + switch textAlign { + case "left": + currentState.textAlign = NSTextAlignment.left + case "right": + currentState.textAlign = NSTextAlignment.right + case "center": + currentState.textAlign = NSTextAlignment.center + default: + currentState.textAlign = NSTextAlignment.left + } + } + + func beginPath() { + path = CGMutablePath() + shapeLayers.removeAll() + } + + func closePath() { + path.closeSubpath() + } + + func lineTo(point: CGPoint) { + path.addLine(to: point) + } + + func moveTo(point: CGPoint) { + path.move(to: point) + } + + func drawCircle(x: CGFloat, y: CGFloat, radius: CGFloat, fill: Bool = false) { + let rect = CGRect(x: x - radius, y: y - radius, width: 2 * radius, height: 2 * radius) + let p = CGMutablePath() + p.addEllipse(in: rect) + var state = CanvasState(state: currentState) + if fill { + state.lineWidth = 0 + } else { + state.fillStyle = UIColor.clear.cgColor + } + let newLayer = ShapeLayerData(path: p, state: state) + sublayers.append(newLayer) + } + + func fillText(text: String, x: CGFloat, y: CGFloat, fill: Bool = true) { + var attrs = [NSAttributedString.Key.font: currentState.font] as [NSAttributedString.Key: Any] + if fill { + attrs[ NSAttributedString.Key.foregroundColor ] = currentState.fillStyle + } else { + attrs[ NSAttributedString.Key.foregroundColor ] = UIColor.clear + attrs[ NSAttributedString.Key.strokeColor ] = currentState.strokeStyle + attrs[ NSAttributedString.Key.strokeWidth ] = currentState.lineWidth + } + let attrString = NSAttributedString(string: text, attributes: attrs) + let textWidth = attrString.size().width + currentState.lineWidth + let textHeight = attrString.size().height + var offsetX = CGFloat(0) // default value for textAlign left (so not needed in switch case) + let offsetY = -1 * textHeight + switch currentState.textAlign { + case NSTextAlignment.right: + offsetX = -1 * textWidth + case NSTextAlignment.center: + offsetX = -1 * textWidth / 2.0 + default: + break + } + let frame = CGRect(x: x + offsetX, y: y + offsetY, width: textWidth, height: textHeight) + let newLayer = TextLayerData(text: attrString, transform: currentState.transform, frame: frame) + sublayers.append(newLayer) + } + + func drawImage(image: IImage, dx: CGFloat, dy: CGFloat) throws { + let frame = CGRect(x: dx, y: dy, width: CGFloat(image.getWidth()), height: CGFloat(image.getHeight())) + if let bitmap = image.getBitmap() { + let newLayer = ImageLayerData(image: bitmap, transform: currentState.transform, frame: frame) + sublayers.append(newLayer) + } else { + throw CanvasError.unableToCreateBitmap + } + } + + func drawImage(image: IImage, dx: CGFloat, dy: CGFloat, dWidth: CGFloat, dHeight: CGFloat) throws { + let frame = CGRect(x: dx, y: dy, width: dWidth, height: dHeight) + if let bitmap = image.getBitmap() { + let newLayer = ImageLayerData(image: bitmap, transform: currentState.transform, frame: frame) + sublayers.append(newLayer) + } else { + throw CanvasError.unableToCreateBitmap + } + } + + func drawImage(image: IImage, + sx: CGFloat, + sy: CGFloat, + sWidth: CGFloat, + sHeight: CGFloat, + dx: CGFloat, + dy: CGFloat, + dWidth: CGFloat, + dHeight: CGFloat) throws { + var contentsImage: CGImage? + if let croppedImage = image.getBitmap()?.cropping(to: CGRect(x: sx, y: sy, width: sWidth, height: sHeight)) { + contentsImage = croppedImage + } else { + contentsImage = image.getBitmap() + } + var frame: CGRect + if dWidth != -1 && dHeight != -1 { + frame = CGRect(x: dx, y: dy, width: dWidth, height: dHeight) + } else { + frame = CGRect(x: dx, y: dy, width: CGFloat(image.getWidth()), height: CGFloat(image.getHeight())) + } + if let bitmap = contentsImage { + let newLayer = ImageLayerData(image: bitmap, transform: currentState.transform, frame: frame) + sublayers.append(newLayer) + } else { + throw CanvasError.unableToCreateBitmap + } + } + + func getImageData(sx: CGFloat, + sy: CGFloat, + sw: CGFloat, + sh: CGFloat, + completionHandler: (ImageData?) -> Void) throws { + let bounds = CGRect(x: sx, y: sy, width: sw, height: sh) + let renderer = UIGraphicsImageRenderer(bounds: bounds) + try DispatchQueue.main.sync { + let uiImage = renderer.image(actions: { rendererContext in + // The sublayers will be empty when the canvas invalidate is called. In that + // case, we want the image data come from the view layer. + if sublayers.isEmpty { + self.layer.render(in: rendererContext.cgContext) } else { - currentState.fontRepresentation = font - } - - var fontName = "" - var serif = false - - if let fontFamilyArr = font["fontFamily"] as? NSArray, let fontFamily = fontFamilyArr[0] as? String { - switch fontFamily { - case "serif": - fontName = "TimesNewRomanPS-" - serif = true - case "sans-serif": - fontName = "HelveticaNeue-" - case "monospace": - fontName = "Menlo-" - default: - throw CanvasError.invalidFontFamily - } - } - - if let fontWeight = font["fontWeight"] as? NSString { - if fontWeight == "bold" { - fontName += "Bold" - } - } - - if let fontStyle = font["fontStyle"] as? NSString { - if fontStyle == "italic" { - fontName += "Italic" - } - } - - if fontName.last == "-" { - fontName.removeLast() - } - - if serif { - fontName += "MT" - } - - currentState.font = UIFont(name: fontName, size: 10.0) ?? .systemFont(ofSize: 10) - - if let fontSizeString = font["fontSize"] as? NSString { - let fontSize = CGFloat(fontSizeString.floatValue) - currentState.font = currentState.font.withSize(fontSize) - } - } - - func setTextAlign(textAlign: String) { - switch textAlign { - case "left": - currentState.textAlign = NSTextAlignment.left - case "right": - currentState.textAlign = NSTextAlignment.right - case "center": - currentState.textAlign = NSTextAlignment.center - default: - currentState.textAlign = NSTextAlignment.left - } - } - - func beginPath() { - path = CGMutablePath() - shapeLayers.removeAll() - } - - func closePath() { - path.closeSubpath() - } - - func lineTo(point: CGPoint) { - path.addLine(to: point) - } - - func moveTo(point: CGPoint) { - path.move(to: point) - } - - func drawCircle(x: CGFloat, y: CGFloat, radius: CGFloat, fill: Bool = false) { - let rect = CGRect(x: x-radius, y: y-radius, width: 2*radius, height: 2*radius) - let p = CGMutablePath() - p.addEllipse(in: rect) - var state = CanvasState(state: currentState) - if fill { - state.lineWidth = 0 - } else { - state.fillStyle = UIColor.clear.cgColor - } - let newLayer = ShapeLayerData(path: p, state: state) - sublayers.append(newLayer) - } - - func fillText(text: String, x: CGFloat, y: CGFloat, fill: Bool = true) { - var attrs = [NSAttributedString.Key.font: currentState.font] as [NSAttributedString.Key: Any] - if fill { - attrs[ NSAttributedString.Key.foregroundColor ] = currentState.fillStyle - } else { - attrs[ NSAttributedString.Key.foregroundColor ] = UIColor.clear - attrs[ NSAttributedString.Key.strokeColor ] = currentState.strokeStyle - attrs[ NSAttributedString.Key.strokeWidth ] = currentState.lineWidth - } - let attrString = NSAttributedString(string: text, attributes: attrs) - let textWidth = attrString.size().width + currentState.lineWidth - let textHeight = attrString.size().height - var offsetX = CGFloat(0) // default value for textAlign left (so not needed in switch case) - let offsetY = -1 * textHeight - switch currentState.textAlign { - case NSTextAlignment.right: - offsetX = -1 * textWidth - case NSTextAlignment.center: - offsetX = -1 * textWidth/2.0 - default: - break - } - let frame = CGRect(x: x + offsetX, y: y + offsetY, width: textWidth, height: textHeight) - let newLayer = TextLayerData(text: attrString, transform: currentState.transform, frame: frame) - sublayers.append(newLayer) - } - - func drawImage(image: IImage, dx: CGFloat, dy: CGFloat) throws { - let frame = CGRect(x: dx, y: dy, width: CGFloat(image.getWidth()), height: CGFloat(image.getHeight())) - if let bitmap = image.getBitmap() { - let newLayer = ImageLayerData(image: bitmap, transform: currentState.transform, frame: frame) - sublayers.append(newLayer) - } else { - throw CanvasError.unableToCreateBitmap - } - } - - func drawImage(image: IImage, dx: CGFloat, dy: CGFloat, dWidth: CGFloat, dHeight: CGFloat) throws { - let frame = CGRect(x: dx, y: dy, width: dWidth, height: dHeight) - if let bitmap = image.getBitmap() { - let newLayer = ImageLayerData(image: bitmap, transform: currentState.transform, frame: frame) - sublayers.append(newLayer) - } else { - throw CanvasError.unableToCreateBitmap - } - } - - func drawImage(image: IImage, - sx: CGFloat, - sy: CGFloat, - sWidth: CGFloat, - sHeight: CGFloat, - dx: CGFloat, - dy: CGFloat, - dWidth: CGFloat, - dHeight: CGFloat) throws { - var contentsImage: CGImage? - if let croppedImage = image.getBitmap()?.cropping(to: CGRect(x: sx, y: sy, width: sWidth, height: sHeight)) { - contentsImage = croppedImage - } else { - contentsImage = image.getBitmap() - } - var frame: CGRect - if dWidth != -1 && dHeight != -1 { - frame = CGRect(x: dx, y: dy, width: dWidth, height: dHeight) - } else { - frame = CGRect(x: dx, y: dy, width: CGFloat(image.getWidth()), height: CGFloat(image.getHeight())) - } - if let bitmap = contentsImage { - let newLayer = ImageLayerData(image: bitmap, transform: currentState.transform, frame: frame) - sublayers.append(newLayer) - } else { - throw CanvasError.unableToCreateBitmap - } - } - - func getImageData(sx: CGFloat, - sy: CGFloat, - sw: CGFloat, - sh: CGFloat, - completionHandler: (ImageData?) -> Void) throws { - let bounds = CGRect(x: sx, y: sy, width: sw, height: sh) - let renderer = UIGraphicsImageRenderer(bounds: bounds) - try DispatchQueue.main.sync { - let uiImage = renderer.image(actions: { rendererContext in - // The sublayers will be empty when the canvas invalidate is called. In that - // case, we want the image data come from the view layer. - if sublayers.isEmpty { - self.layer.render(in: rendererContext.cgContext) - } else { - let rootLayer = CALayer() - rootLayer.bounds = self.bounds - self.renderLayers(allLayerData: sublayers, layer: rootLayer) - rootLayer.render(in: rendererContext.cgContext) - } - }) - guard let bitmap = uiImage.cgImage else { - throw CanvasError.unableToCreateBitmap - } - let imageData = ImageData(bitmap: bitmap, scaledWidth: sw, scaledHeight: sh) - completionHandler(imageData) - } - } - - func putImageData(imageData: ImageData, sx: CGFloat, sy: CGFloat) throws { - let newLayer = ImageLayerData(image: imageData.bitmap, transform: currentState.transform, frame: frame) - sublayers.append(newLayer) - } - - func onTransformationChange() { - let newLayer = ShapeLayerData(path: path, state: currentState) - shapeLayers.append(newLayer) - let startPoint = path.currentPoint - path = CGMutablePath() - path.move(to: startPoint, transform: CATransform3DGetAffineTransform(currentState.transform)) - } - - class Stack { - var stateArray = [CanvasState]() - - func push(state: CanvasState) { - stateArray.append(state) - } - - func pop() -> CanvasState? { - if stateArray.last != nil { - return stateArray.removeLast() - } else { - return nil - } - } - } + let rootLayer = CALayer() + rootLayer.bounds = self.bounds + self.renderLayers(allLayerData: sublayers, layer: rootLayer) + rootLayer.render(in: rendererContext.cgContext) + } + }) + guard let bitmap = uiImage.cgImage else { + throw CanvasError.unableToCreateBitmap + } + let imageData = ImageData(bitmap: bitmap, scaledWidth: sw, scaledHeight: sh) + completionHandler(imageData) + } + } + + func putImageData(imageData: ImageData, sx: CGFloat, sy: CGFloat) throws { + let newLayer = ImageLayerData(image: imageData.bitmap, transform: currentState.transform, frame: frame) + sublayers.append(newLayer) + } + + func onTransformationChange() { + let newLayer = ShapeLayerData(path: path, state: currentState) + shapeLayers.append(newLayer) + let startPoint = path.currentPoint + path = CGMutablePath() + path.move(to: startPoint, transform: CATransform3DGetAffineTransform(currentState.transform)) + } + + class Stack { + var stateArray = [CanvasState]() + + func push(state: CanvasState) { + stateArray.append(state) + } + + func pop() -> CanvasState? { + if stateArray.last != nil { + return stateArray.removeLast() + } else { + return nil + } + } + } } struct CanvasState { - public var transform: CATransform3D - public var strokeStyle: CGColor - public var fillStyle: CGColor - public var lineWidth: CGFloat - public var lineCap: CAShapeLayerLineCap - public var lineJoin: CAShapeLayerLineJoin - public var miterLimit: CGFloat - public var font: UIFont - public var textAlign: NSTextAlignment - public var fontRepresentation: NSDictionary? - - init(transform: CATransform3D = CATransform3DIdentity, - strokeStyle: CGColor = UIColor.black.cgColor, - fillStyle: CGColor = UIColor.black.cgColor, - lineWidth: CGFloat = 1, - lineCap: CAShapeLayerLineCap = .butt, - lineJoin: CAShapeLayerLineJoin = .miter, - miterLimit: CGFloat = 10, - font: UIFont = .systemFont(ofSize: 10), - textAlign: NSTextAlignment = NSTextAlignment.left) { - self.transform = transform - self.strokeStyle = strokeStyle - self.fillStyle = fillStyle - self.lineWidth = lineWidth - self.lineCap = lineCap - self.lineJoin = lineJoin - self.miterLimit = miterLimit - self.font = font - self.textAlign = textAlign - } - - init(state: CanvasState) { - self.transform = CATransform3DConcat(state.transform, CATransform3DIdentity) - self.strokeStyle = state.strokeStyle.copy()! - self.fillStyle = state.fillStyle.copy()! - self.lineWidth = CGFloat(state.lineWidth) - self.lineCap = CAShapeLayerLineCap(rawValue: state.lineCap.rawValue) - self.lineJoin = CAShapeLayerLineJoin(rawValue: state.lineJoin.rawValue) - self.miterLimit = CGFloat(state.miterLimit) - // swiftlint:disable:next force_cast - self.font = state.font.copy() as! UIFont - self.textAlign = state.textAlign - self.fontRepresentation = state.fontRepresentation - } + public var transform: CATransform3D + public var strokeStyle: CGColor + public var fillStyle: CGColor + public var lineWidth: CGFloat + public var lineCap: CAShapeLayerLineCap + public var lineJoin: CAShapeLayerLineJoin + public var miterLimit: CGFloat + public var font: UIFont + public var textAlign: NSTextAlignment + public var fontRepresentation: NSDictionary? + + init(transform: CATransform3D = CATransform3DIdentity, + strokeStyle: CGColor = UIColor.black.cgColor, + fillStyle: CGColor = UIColor.black.cgColor, + lineWidth: CGFloat = 1, + lineCap: CAShapeLayerLineCap = .butt, + lineJoin: CAShapeLayerLineJoin = .miter, + miterLimit: CGFloat = 10, + font: UIFont = .systemFont(ofSize: 10), + textAlign: NSTextAlignment = NSTextAlignment.left) { + self.transform = transform + self.strokeStyle = strokeStyle + self.fillStyle = fillStyle + self.lineWidth = lineWidth + self.lineCap = lineCap + self.lineJoin = lineJoin + self.miterLimit = miterLimit + self.font = font + self.textAlign = textAlign + } + + init(state: CanvasState) { + self.transform = CATransform3DConcat(state.transform, CATransform3DIdentity) + self.strokeStyle = state.strokeStyle.copy()! + self.fillStyle = state.fillStyle.copy()! + self.lineWidth = CGFloat(state.lineWidth) + self.lineCap = CAShapeLayerLineCap(rawValue: state.lineCap.rawValue) + self.lineJoin = CAShapeLayerLineJoin(rawValue: state.lineJoin.rawValue) + self.miterLimit = CGFloat(state.miterLimit) + // swiftlint:disable:next force_cast + self.font = state.font.copy() as! UIFont + self.textAlign = state.textAlign + self.fontRepresentation = state.fontRepresentation + } } diff --git a/react-native-pytorch-core/ios/Canvas/ImageData.swift b/react-native-pytorch-core/ios/Canvas/ImageData.swift index c7e5da26f..b4528cbd7 100644 --- a/react-native-pytorch-core/ios/Canvas/ImageData.swift +++ b/react-native-pytorch-core/ios/Canvas/ImageData.swift @@ -8,26 +8,26 @@ import Foundation class ImageData { - enum ImageDataError: Error { - case scaleImage - } + enum ImageDataError: Error { + case scaleImage + } - let bitmap: CGImage - let scaledWidth: CGFloat - let scaledHeight: CGFloat + let bitmap: CGImage + let scaledWidth: CGFloat + let scaledHeight: CGFloat - init(bitmap: CGImage, scaledWidth: CGFloat, scaledHeight: CGFloat) { - self.bitmap = bitmap - self.scaledWidth = scaledWidth - self.scaledHeight = scaledHeight - } + init(bitmap: CGImage, scaledWidth: CGFloat, scaledHeight: CGFloat) { + self.bitmap = bitmap + self.scaledWidth = scaledWidth + self.scaledHeight = scaledHeight + } - func getScaledBitmap() throws -> CGImage { - let size = CGSize(width: self.scaledWidth, height: self.scaledHeight) - guard let scaledImage = UIImage(cgImage: self.bitmap).resizeImage(size: size), - let scaledBitmap = scaledImage.cgImage else { - throw ImageDataError.scaleImage - } - return scaledBitmap + func getScaledBitmap() throws -> CGImage { + let size = CGSize(width: self.scaledWidth, height: self.scaledHeight) + guard let scaledImage = UIImage(cgImage: self.bitmap).resizeImage(size: size), + let scaledBitmap = scaledImage.cgImage else { + throw ImageDataError.scaleImage } + return scaledBitmap + } } diff --git a/react-native-pytorch-core/ios/Canvas/ImageDataModule.swift b/react-native-pytorch-core/ios/Canvas/ImageDataModule.swift index 28b63b574..418555a0a 100644 --- a/react-native-pytorch-core/ios/Canvas/ImageDataModule.swift +++ b/react-native-pytorch-core/ios/Canvas/ImageDataModule.swift @@ -10,22 +10,22 @@ import Foundation @objc(ImageDataModule) public class ImageDataModule: NSObject { - @objc - public func getName() -> String { - return "PyTorchCoreImageDataModule" - } + @objc + public func getName() -> String { + return "PyTorchCoreImageDataModule" + } - @objc(release:resolver:rejecter:) - public func release(_ imageDataRef: NSDictionary, - resolver resolve: RCTPromiseResolveBlock, - rejecter reject: RCTPromiseRejectBlock) { - do { - if let imageDataRef = imageDataRef as? [ String: String] { - try JSContext.release(jsRef: imageDataRef) - } - resolve(nil) - } catch { - reject(RCTErrorUnspecified, "Invalid image data reference in release: \(error)", error) - } + @objc(release:resolver:rejecter:) + public func release(_ imageDataRef: NSDictionary, + resolver resolve: RCTPromiseResolveBlock, + rejecter reject: RCTPromiseRejectBlock) { + do { + if let imageDataRef = imageDataRef as? [ String: String] { + try JSContext.release(jsRef: imageDataRef) + } + resolve(nil) + } catch { + reject(RCTErrorUnspecified, "Invalid image data reference in release: \(error)", error) } + } } diff --git a/react-native-pytorch-core/ios/Canvas/LayerData.swift b/react-native-pytorch-core/ios/Canvas/LayerData.swift index ace98de03..655a2bf40 100644 --- a/react-native-pytorch-core/ios/Canvas/LayerData.swift +++ b/react-native-pytorch-core/ios/Canvas/LayerData.swift @@ -8,51 +8,50 @@ import Foundation enum LayerType { - case textLayer - case shapeLayer - case imageLayer + case textLayer + case shapeLayer + case imageLayer } class LayerData { - let type: LayerType - let transform: CATransform3D - - init(layerType: LayerType, transform: CATransform3D) { - self.type = layerType - self.transform = transform - } + let type: LayerType + let transform: CATransform3D + init(layerType: LayerType, transform: CATransform3D) { + self.type = layerType + self.transform = transform + } } class TextLayerData: LayerData { - let text: NSAttributedString - let frame: CGRect - - init(text: NSAttributedString, transform: CATransform3D, frame: CGRect) { - self.text = text - self.frame = frame - super.init(layerType: LayerType.textLayer, transform: transform) - } + let text: NSAttributedString + let frame: CGRect + + init(text: NSAttributedString, transform: CATransform3D, frame: CGRect) { + self.text = text + self.frame = frame + super.init(layerType: LayerType.textLayer, transform: transform) + } } class ShapeLayerData: LayerData { - let path: CGPath - let state: CanvasState - - init(path: CGPath, state: CanvasState) { - self.path = path - self.state = state - super.init(layerType: LayerType.shapeLayer, transform: state.transform) - } + let path: CGPath + let state: CanvasState + + init(path: CGPath, state: CanvasState) { + self.path = path + self.state = state + super.init(layerType: LayerType.shapeLayer, transform: state.transform) + } } class ImageLayerData: LayerData { - let image: CGImage - let frame: CGRect - - init(image: CGImage, transform: CATransform3D, frame: CGRect) { - self.image = image - self.frame = frame - super.init(layerType: LayerType.imageLayer, transform: transform) - } + let image: CGImage + let frame: CGRect + + init(image: CGImage, transform: CATransform3D, frame: CGRect) { + self.image = image + self.frame = frame + super.init(layerType: LayerType.imageLayer, transform: transform) + } } diff --git a/react-native-pytorch-core/ios/Canvas/ShapeLayer.swift b/react-native-pytorch-core/ios/Canvas/ShapeLayer.swift index 80680b501..1c1ab3abd 100644 --- a/react-native-pytorch-core/ios/Canvas/ShapeLayer.swift +++ b/react-native-pytorch-core/ios/Canvas/ShapeLayer.swift @@ -43,21 +43,21 @@ struct ShapeLayer { circlePath.addArc(withCenter: center, radius: radius, startAngle: -CGFloat(Double.pi), - endAngle: -CGFloat(Double.pi/2), + endAngle: -CGFloat(Double.pi / 2), clockwise: true) circlePath.addArc(withCenter: center, radius: radius, - startAngle: -CGFloat(Double.pi/2), + startAngle: -CGFloat(Double.pi / 2), endAngle: 0, clockwise: true) circlePath.addArc(withCenter: center, radius: radius, startAngle: 0, - endAngle: CGFloat(Double.pi/2), + endAngle: CGFloat(Double.pi / 2), clockwise: true) circlePath.addArc(withCenter: center, radius: radius, - startAngle: CGFloat(Double.pi/2), + startAngle: CGFloat(Double.pi / 2), endAngle: CGFloat(Double.pi), clockwise: true) circlePath.close() @@ -84,5 +84,4 @@ struct ShapeLayer { squarePath.close() return squarePath } - } diff --git a/react-native-pytorch-core/ios/Image/BitmapImage.swift b/react-native-pytorch-core/ios/Image/BitmapImage.swift index 70debed3a..771b2bac1 100644 --- a/react-native-pytorch-core/ios/Image/BitmapImage.swift +++ b/react-native-pytorch-core/ios/Image/BitmapImage.swift @@ -8,62 +8,62 @@ import Foundation public class BitmapImage: IImage { - private var mBitmap: CGImage? - private var ciImage: CIImage? - public var width: CGFloat - public var height: CGFloat + private var mBitmap: CGImage? + private var ciImage: CIImage? + public var width: CGFloat + public var height: CGFloat - init(image: CGImage) { - self.mBitmap = image - self.width = CGFloat(truncating: image.width as NSNumber) - self.height = CGFloat(truncating: image.height as NSNumber) - } + init(image: CGImage) { + self.mBitmap = image + self.width = CGFloat(truncating: image.width as NSNumber) + self.height = CGFloat(truncating: image.height as NSNumber) + } - init(image: CIImage) { - self.ciImage = image - self.width = image.extent.width - self.height = image.extent.height - } + init(image: CIImage) { + self.ciImage = image + self.width = image.extent.width + self.height = image.extent.height + } - public func getPixelDensity() -> CGFloat { - return 1.0 - } + public func getPixelDensity() -> CGFloat { + return 1.0 + } - public func getWidth() -> CGFloat { - return self.width - } + public func getWidth() -> CGFloat { + return self.width + } - public func getHeight() -> CGFloat { - return self.height - } + public func getHeight() -> CGFloat { + return self.height + } - public func getNaturalWidth() -> Int { - return Int(self.width) - } + public func getNaturalWidth() -> Int { + return Int(self.width) + } - public func getNaturalHeight() -> Int { - return Int(self.height) - } + public func getNaturalHeight() -> Int { + return Int(self.height) + } - public func scale(sx: CGFloat, sy: CGFloat) throws -> IImage { - return try ImageUtils.scale(image: self, sx: sx, sy: sy) - } + public func scale(sx: CGFloat, sy: CGFloat) throws -> IImage { + return try ImageUtils.scale(image: self, sx: sx, sy: sy) + } - public func getBitmap() -> CGImage? { - if let bitmap = mBitmap { - return bitmap - } else { - let context = CIContext() - if let ciImage = ciImage, let bitmap = context.createCGImage(ciImage, from: ciImage.extent) { - mBitmap = bitmap - return bitmap - } else { - return nil - } - } + public func getBitmap() -> CGImage? { + if let bitmap = mBitmap { + return bitmap + } else { + let context = CIContext() + if let ciImage = ciImage, let bitmap = context.createCGImage(ciImage, from: ciImage.extent) { + mBitmap = bitmap + return bitmap + } else { + return nil + } } + } - public func close() throws { - // TODO(T94684939) - } + public func close() throws { + // TODO(T94684939) + } } diff --git a/react-native-pytorch-core/ios/Image/IImage.swift b/react-native-pytorch-core/ios/Image/IImage.swift index e378ff52f..0b6b8e541 100644 --- a/react-native-pytorch-core/ios/Image/IImage.swift +++ b/react-native-pytorch-core/ios/Image/IImage.swift @@ -8,12 +8,12 @@ import Foundation public protocol IImage { - func getPixelDensity() -> CGFloat - func getWidth() -> CGFloat - func getHeight() -> CGFloat - func getNaturalWidth() -> Int - func getNaturalHeight() -> Int - func scale(sx: CGFloat, sy: CGFloat) throws -> IImage - func getBitmap() -> CGImage? - func close() throws + func getPixelDensity() -> CGFloat + func getWidth() -> CGFloat + func getHeight() -> CGFloat + func getNaturalWidth() -> Int + func getNaturalHeight() -> Int + func scale(sx: CGFloat, sy: CGFloat) throws -> IImage + func getBitmap() -> CGImage? + func close() throws } diff --git a/react-native-pytorch-core/ios/Image/Image.swift b/react-native-pytorch-core/ios/Image/Image.swift index e7e0cd9a7..af653883a 100644 --- a/react-native-pytorch-core/ios/Image/Image.swift +++ b/react-native-pytorch-core/ios/Image/Image.swift @@ -8,65 +8,65 @@ import Foundation enum ImageError: Error { - case scale + case scale } public class Image: IImage { - private var image: IImage - private var closed: Bool = false - - init(image: CGImage) { - self.image = BitmapImage(image: image) - } - - init(image: CIImage) { - self.image = BitmapImage(image: image) - } - - init(imageData: ImageData, pixelDensity: CGFloat) { - self.image = ImageDataImage(imageData: imageData, pixelDensity: pixelDensity) - } - - public func getPixelDensity() -> CGFloat { - return self.image.getPixelDensity() - } - - public func getWidth() -> CGFloat { - return self.image.getWidth() - } - - public func getHeight() -> CGFloat { - return self.image.getHeight() - } - - public func getNaturalWidth() -> Int { - // Note: on Android, returns the width in device pixels so that it can be properly redrawn back to the canvas - // Since iOS always uses points (density independent), this will return the same as getWidth so that it can - // be properly redrawn back to the canvas - return self.image.getNaturalWidth() - } - - public func getNaturalHeight() -> Int { - // Note: on Android, returns the height in device pixels so that it can be properly redrawn back to the canvas - // Since iOS always uses points (density independent), this will return the same as getHeight so that it can - // be properly redrawn back to the canvas - return self.image.getNaturalHeight() - } - - public func scale(sx: CGFloat, sy: CGFloat) throws -> IImage { - return try self.image.scale(sx: sx, sy: sy) - } - - public func getBitmap() -> CGImage? { - return self.image.getBitmap() - } - - public func close() throws { - self.closed = true - } - - public func isClosed() -> Bool { - return self.closed - } + private var image: IImage + private var closed: Bool = false + + init(image: CGImage) { + self.image = BitmapImage(image: image) + } + + init(image: CIImage) { + self.image = BitmapImage(image: image) + } + + init(imageData: ImageData, pixelDensity: CGFloat) { + self.image = ImageDataImage(imageData: imageData, pixelDensity: pixelDensity) + } + + public func getPixelDensity() -> CGFloat { + return self.image.getPixelDensity() + } + + public func getWidth() -> CGFloat { + return self.image.getWidth() + } + + public func getHeight() -> CGFloat { + return self.image.getHeight() + } + + public func getNaturalWidth() -> Int { + // Note: on Android, returns the width in device pixels so that it can be properly redrawn back to the canvas + // Since iOS always uses points (density independent), this will return the same as getWidth so that it can + // be properly redrawn back to the canvas + return self.image.getNaturalWidth() + } + + public func getNaturalHeight() -> Int { + // Note: on Android, returns the height in device pixels so that it can be properly redrawn back to the canvas + // Since iOS always uses points (density independent), this will return the same as getHeight so that it can + // be properly redrawn back to the canvas + return self.image.getNaturalHeight() + } + + public func scale(sx: CGFloat, sy: CGFloat) throws -> IImage { + return try self.image.scale(sx: sx, sy: sy) + } + + public func getBitmap() -> CGImage? { + return self.image.getBitmap() + } + + public func close() throws { + self.closed = true + } + + public func isClosed() -> Bool { + return self.closed + } } diff --git a/react-native-pytorch-core/ios/Image/ImageDataImage.swift b/react-native-pytorch-core/ios/Image/ImageDataImage.swift index 57d31767b..56854ca79 100644 --- a/react-native-pytorch-core/ios/Image/ImageDataImage.swift +++ b/react-native-pytorch-core/ios/Image/ImageDataImage.swift @@ -8,47 +8,47 @@ import Foundation public class ImageDataImage: IImage { - private var imageData: ImageData - private var pixelDensity: CGFloat + private var imageData: ImageData + private var pixelDensity: CGFloat - init(imageData: ImageData, pixelDensity: CGFloat) { - self.imageData = imageData - self.pixelDensity = pixelDensity - } + init(imageData: ImageData, pixelDensity: CGFloat) { + self.imageData = imageData + self.pixelDensity = pixelDensity + } - public func getPixelDensity() -> CGFloat { - return self.pixelDensity - } + public func getPixelDensity() -> CGFloat { + return self.pixelDensity + } - public func getWidth() -> CGFloat { - return CGFloat(self.imageData.bitmap.width) - } + public func getWidth() -> CGFloat { + return CGFloat(self.imageData.bitmap.width) + } - public func getHeight() -> CGFloat { - return CGFloat(self.imageData.bitmap.height) - } + public func getHeight() -> CGFloat { + return CGFloat(self.imageData.bitmap.height) + } - public func getNaturalWidth() -> Int { - return Int(self.imageData.bitmap.width) - } + public func getNaturalWidth() -> Int { + return Int(self.imageData.bitmap.width) + } - public func getNaturalHeight() -> Int { - return Int(self.imageData.bitmap.height) - } + public func getNaturalHeight() -> Int { + return Int(self.imageData.bitmap.height) + } - public func scale(sx: CGFloat, sy: CGFloat) throws -> IImage { - if let cgImage = self.getBitmap() { - let bitmapImage = Image(image: cgImage) - return try ImageUtils.scale(image: bitmapImage, sx: sx, sy: sy) - } - throw ImageError.scale + public func scale(sx: CGFloat, sy: CGFloat) throws -> IImage { + if let cgImage = self.getBitmap() { + let bitmapImage = Image(image: cgImage) + return try ImageUtils.scale(image: bitmapImage, sx: sx, sy: sy) } + throw ImageError.scale + } - public func getBitmap() -> CGImage? { - return self.imageData.bitmap - } + public func getBitmap() -> CGImage? { + return self.imageData.bitmap + } - public func close() throws { - // TODO(T94684939) - } + public func close() throws { + // TODO(T94684939) + } } diff --git a/react-native-pytorch-core/ios/Image/ImageModule.swift b/react-native-pytorch-core/ios/Image/ImageModule.swift index 9fbeaeb13..d4601df36 100644 --- a/react-native-pytorch-core/ios/Image/ImageModule.swift +++ b/react-native-pytorch-core/ios/Image/ImageModule.swift @@ -10,181 +10,181 @@ import UIKit @objc(ImageModule) public class ImageModule: NSObject { - @objc - public func getName() -> String { - return "PyTorchCoreImageModule" + @objc + public func getName() -> String { + return "PyTorchCoreImageModule" + } + + @objc(fromURL:resolver:rejecter:) + public func fromURL(_ urlString: NSString, + resolver resolve: RCTPromiseResolveBlock, + rejecter reject: RCTPromiseRejectBlock) { + if let cfURL = CFURLCreateWithString(nil, urlString, nil), + let imageSource = CGImageSourceCreateWithURL(cfURL, nil), + let image = CGImageSourceCreateImageAtIndex(imageSource, 0, nil) { + let bitmapImage = Image(image: image) + let ref = JSContext.wrapObject(object: bitmapImage).getJSRef() + resolve(ref) + } else { + reject(RCTErrorUnspecified, "Couldn't create image from URL", nil) } + } - @objc(fromURL:resolver:rejecter:) - public func fromURL(_ urlString: NSString, - resolver resolve: RCTPromiseResolveBlock, - rejecter reject: RCTPromiseRejectBlock) { - if let cfURL = CFURLCreateWithString(nil, urlString, nil), - let imageSource = CGImageSourceCreateWithURL(cfURL, nil), - let image = CGImageSourceCreateImageAtIndex(imageSource, 0, nil) { - let bitmapImage = Image(image: image) - let ref = JSContext.wrapObject(object: bitmapImage).getJSRef() - resolve(ref) - } else { - reject(RCTErrorUnspecified, "Couldn't create image from URL", nil) - } + @objc(fromFile:resolver:rejecter:) + public func fromFile(_ filepath: NSString, + resolver resolve: RCTPromiseResolveBlock, + rejecter reject: RCTPromiseRejectBlock) { + let path = filepath as String + let url = URL(fileURLWithPath: path) + do { + let data = try Data(contentsOf: url) + guard let uiImage = UIImage(data: data), let cgImage = uiImage.forceSameOrientation().cgImage else { + reject(RCTErrorUnspecified, "Couldn't load image \(path)", nil) + return + } + let image = Image(image: cgImage) + let ref = JSContext.wrapObject(object: image).getJSRef() + resolve(ref) + } catch { + reject(RCTErrorUnspecified, "Couldn't load file \(path)", nil) } + } - @objc(fromFile:resolver:rejecter:) - public func fromFile(_ filepath: NSString, + @objc(fromBundle:resolver:rejecter:) + public func fromBundle(_ assetImage: NSDictionary, resolver resolve: RCTPromiseResolveBlock, rejecter reject: RCTPromiseRejectBlock) { - let path = filepath as String - let url = URL(fileURLWithPath: path) - do { - let data = try Data(contentsOf: url) - guard let uiImage = UIImage(data: data), let cgImage = uiImage.forceSameOrientation().cgImage else { - reject(RCTErrorUnspecified, "Couldn't load image \(path)", nil) - return - } - let image = Image(image: cgImage) - let ref = JSContext.wrapObject(object: image).getJSRef() - resolve(ref) - } catch { - reject(RCTErrorUnspecified, "Couldn't load file \(path)", nil) - } - } - - @objc(fromBundle:resolver:rejecter:) - public func fromBundle(_ assetImage: NSDictionary, - resolver resolve: RCTPromiseResolveBlock, - rejecter reject: RCTPromiseRejectBlock) { - DispatchQueue.main.sync { - if let dictionary = assetImage as? [AnyHashable: Any] { - let uiImage = Macros.toUIImage(dictionary) - if let cgImage = uiImage.forceSameOrientation().cgImage { - let bitmapImage = Image(image: cgImage) - let ref = JSContext.wrapObject(object: bitmapImage).getJSRef() - resolve(ref) - } - } else { - reject(RCTErrorUnspecified, "Couldn't create image from bundle", nil) - } + DispatchQueue.main.sync { + if let dictionary = assetImage as? [AnyHashable: Any] { + let uiImage = Macros.toUIImage(dictionary) + if let cgImage = uiImage.forceSameOrientation().cgImage { + let bitmapImage = Image(image: cgImage) + let ref = JSContext.wrapObject(object: bitmapImage).getJSRef() + resolve(ref) } + } else { + reject(RCTErrorUnspecified, "Couldn't create image from bundle", nil) + } } + } - @objc(fromImageData:scaled:resolver:rejecter:) - public func fromImageData(_ imageDataRef: NSDictionary, - scaled: Bool, - resolver resolve: RCTPromiseResolveBlock, - rejecter reject: RCTPromiseRejectBlock) { - DispatchQueue.main.sync { - do { - let imageData = try JSContextUtils.unwrapObject(imageDataRef, ImageData.self) - - var image: IImage - if scaled { - let bitmap = try imageData.getScaledBitmap() - image = Image(image: bitmap) - } else { - let pixelDensity = UIScreen.main.scale - image = Image(imageData: imageData, pixelDensity: pixelDensity) - } + @objc(fromImageData:scaled:resolver:rejecter:) + public func fromImageData(_ imageDataRef: NSDictionary, + scaled: Bool, + resolver resolve: RCTPromiseResolveBlock, + rejecter reject: RCTPromiseRejectBlock) { + DispatchQueue.main.sync { + do { + let imageData = try JSContextUtils.unwrapObject(imageDataRef, ImageData.self) - let ref = JSContext.wrapObject(object: image).getJSRef() - resolve(ref) - } catch { - reject(RCTErrorUnspecified, "Could't create image from image data: \(error)", error) - } + var image: IImage + if scaled { + let bitmap = try imageData.getScaledBitmap() + image = Image(image: bitmap) + } else { + let pixelDensity = UIScreen.main.scale + image = Image(imageData: imageData, pixelDensity: pixelDensity) } + + let ref = JSContext.wrapObject(object: image).getJSRef() + resolve(ref) + } catch { + reject(RCTErrorUnspecified, "Could't create image from image data: \(error)", error) + } } + } - @objc(toFile:resolver:rejecter:) - public func toFile(_ imageRef: NSDictionary, - resolver resolve: RCTPromiseResolveBlock, - rejecter reject: RCTPromiseRejectBlock) { - do { - let image = try JSContextUtils.unwrapObject(imageRef, IImage.self) - guard let bitmap = image.getBitmap() else { - print("Could not get bitmap from image") - return - } - let uiImage = UIImage(cgImage: bitmap) - if let data = uiImage.pngData() { - let uuidFilename = NSUUID().uuidString - let filename = getDocumentsDirectory().appendingPathComponent("\(uuidFilename).png") - try? data.write(to: filename) - resolve(filename.path) - } - } catch { - reject(RCTErrorUnspecified, "Invalid image reference \(error)", error) - return - } + @objc(toFile:resolver:rejecter:) + public func toFile(_ imageRef: NSDictionary, + resolver resolve: RCTPromiseResolveBlock, + rejecter reject: RCTPromiseRejectBlock) { + do { + let image = try JSContextUtils.unwrapObject(imageRef, IImage.self) + guard let bitmap = image.getBitmap() else { + print("Could not get bitmap from image") + return + } + let uiImage = UIImage(cgImage: bitmap) + if let data = uiImage.pngData() { + let uuidFilename = NSUUID().uuidString + let filename = getDocumentsDirectory().appendingPathComponent("\(uuidFilename).png") + try? data.write(to: filename) + resolve(filename.path) + } + } catch { + reject(RCTErrorUnspecified, "Invalid image reference \(error)", error) + return } + } - func getDocumentsDirectory() -> URL { - let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask) - return paths[0] + func getDocumentsDirectory() -> URL { + let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask) + return paths[0] + } + + @objc + public func getWidth(_ imageRef: NSDictionary) -> Any { + do { + let image = try JSContextUtils.unwrapObject(imageRef, IImage.self) + return NSNumber(value: Float(image.getWidth())) + } catch { + print("Invalid image reference in getWidth") + return -1 } + } - @objc - public func getWidth(_ imageRef: NSDictionary) -> Any { - do { - let image = try JSContextUtils.unwrapObject(imageRef, IImage.self) - return NSNumber(value: Float(image.getWidth())) - } catch { - print("Invalid image reference in getWidth") - return -1 - } + @objc + public func getHeight(_ imageRef: NSDictionary) -> Any { + do { + let image = try JSContextUtils.unwrapObject(imageRef, IImage.self) + return NSNumber(value: Float(image.getHeight())) + } catch { + print("Invalid image reference in getHeight") + return -1 } + } - @objc - public func getHeight(_ imageRef: NSDictionary) -> Any { - do { - let image = try JSContextUtils.unwrapObject(imageRef, IImage.self) - return NSNumber(value: Float(image.getHeight())) - } catch { - print("Invalid image reference in getHeight") - return -1 - } + @objc(scale:sx:sy:resolver:rejecter:) + public func scale(_ imageRef: NSDictionary, + sx: NSNumber, + sy: NSNumber, + resolver resolve: RCTPromiseResolveBlock, + rejecter reject: RCTPromiseRejectBlock) { + do { + let image = try JSContextUtils.unwrapObject(imageRef, IImage.self) + let scaledImage = try image.scale(sx: CGFloat(truncating: sx), sy: CGFloat(truncating: sy)) + let ref = JSContext.wrapObject(object: scaledImage).getJSRef() + resolve(ref) + } catch { + reject(RCTErrorUnspecified, "Invalid image reference in scale: \(error)", error) } + } - @objc(scale:sx:sy:resolver:rejecter:) - public func scale(_ imageRef: NSDictionary, - sx: NSNumber, - sy: NSNumber, + @objc(release:resolver:rejecter:) + public func release(_ imageRef: NSDictionary, resolver resolve: RCTPromiseResolveBlock, rejecter reject: RCTPromiseRejectBlock) { - do { - let image = try JSContextUtils.unwrapObject(imageRef, IImage.self) - let scaledImage = try image.scale(sx: CGFloat(truncating: sx), sy: CGFloat(truncating: sy)) - let ref = JSContext.wrapObject(object: scaledImage).getJSRef() - resolve(ref) - } catch { - reject(RCTErrorUnspecified, "Invalid image reference in scale: \(error)", error) - } - } - - @objc(release:resolver:rejecter:) - public func release(_ imageRef: NSDictionary, - resolver resolve: RCTPromiseResolveBlock, - rejecter reject: RCTPromiseRejectBlock) { - do { - if let imageRef = imageRef as? [ String: String] { - try JSContext.release(jsRef: imageRef) - } - resolve(nil) - } catch { - reject(RCTErrorUnspecified, "Invalid image reference in release: \(error)", error) - } + do { + if let imageRef = imageRef as? [ String: String] { + try JSContext.release(jsRef: imageRef) + } + resolve(nil) + } catch { + reject(RCTErrorUnspecified, "Invalid image reference in release: \(error)", error) } + } - @objc(wrapImage:) - public static func wrapImage(_ image: UIImage) -> NSString? { - if let cgImage = image.cgImage { - let bitmapImage = Image(image: cgImage) - let ref = JSContext.wrapObject(object: bitmapImage).getJSRef() - guard let refID = ref["ID"] as? NSString else { - return nil - } - return refID - } else { + @objc(wrapImage:) + public static func wrapImage(_ image: UIImage) -> NSString? { + if let cgImage = image.cgImage { + let bitmapImage = Image(image: cgImage) + let ref = JSContext.wrapObject(object: bitmapImage).getJSRef() + guard let refID = ref["ID"] as? NSString else { return nil } + return refID + } else { + return nil } + } } diff --git a/react-native-pytorch-core/ios/Image/ImageUtils.swift b/react-native-pytorch-core/ios/Image/ImageUtils.swift index 109b9d2f6..2986c45d6 100644 --- a/react-native-pytorch-core/ios/Image/ImageUtils.swift +++ b/react-native-pytorch-core/ios/Image/ImageUtils.swift @@ -8,32 +8,32 @@ import Foundation class ImageUtils { - static func scale(image: IImage, sx: CGFloat, sy: CGFloat) throws -> IImage { - if let bitmap = image.getBitmap() { + static func scale(image: IImage, sx: CGFloat, sy: CGFloat) throws -> IImage { + if let bitmap = image.getBitmap() { - let width = Float(bitmap.width) * Float(sx) - let height = Float(bitmap.height) * Float(sy) + let width = Float(bitmap.width) * Float(sx) + let height = Float(bitmap.height) * Float(sy) - let size = CGSize(width: Int(width), height: Int(height)) - let rect = CGRect(x: 0, y: 0, width: size.width, height: size.height) + let size = CGSize(width: Int(width), height: Int(height)) + let rect = CGRect(x: 0, y: 0, width: size.width, height: size.height) - let uiImage = UIImage(cgImage: bitmap) + let uiImage = UIImage(cgImage: bitmap) - let renderFormat = UIGraphicsImageRendererFormat.default() - renderFormat.opaque = false - renderFormat.scale = 1 - let renderer = UIGraphicsImageRenderer( - size: size, - format: renderFormat - ) - let scaledImage = renderer.image { (_) in - uiImage.draw(in: rect) - } - guard let newBitmap = scaledImage.cgImage else { - throw ImageError.scale - } - return BitmapImage(image: newBitmap) - } + let renderFormat = UIGraphicsImageRendererFormat.default() + renderFormat.opaque = false + renderFormat.scale = 1 + let renderer = UIGraphicsImageRenderer( + size: size, + format: renderFormat + ) + let scaledImage = renderer.image { (_) in + uiImage.draw(in: rect) + } + guard let newBitmap = scaledImage.cgImage else { throw ImageError.scale + } + return BitmapImage(image: newBitmap) } + throw ImageError.scale + } } diff --git a/react-native-pytorch-core/ios/Image/UIImage+Orientation.swift b/react-native-pytorch-core/ios/Image/UIImage+Orientation.swift index e4b773ad1..a835f1767 100644 --- a/react-native-pytorch-core/ios/Image/UIImage+Orientation.swift +++ b/react-native-pytorch-core/ios/Image/UIImage+Orientation.swift @@ -10,18 +10,18 @@ import UIKit extension UIImage { - func forceSameOrientation() -> UIImage { - // Already oriented upright, so no need to convert - if self.imageOrientation == UIImage.Orientation.up { - return self - } - UIGraphicsBeginImageContext(self.size) - self.draw(in: CGRect(origin: CGPoint.zero, size: self.size)) - guard let image = UIGraphicsGetImageFromCurrentImageContext() else { - UIGraphicsEndImageContext() - return self - } - UIGraphicsEndImageContext() - return image + func forceSameOrientation() -> UIImage { + // Already oriented upright, so no need to convert + if self.imageOrientation == UIImage.Orientation.up { + return self } + UIGraphicsBeginImageContext(self.size) + self.draw(in: CGRect(origin: CGPoint.zero, size: self.size)) + guard let image = UIGraphicsGetImageFromCurrentImageContext() else { + UIGraphicsEndImageContext() + return self + } + UIGraphicsEndImageContext() + return image + } } diff --git a/react-native-pytorch-core/ios/Image/UIImage+Resize.swift b/react-native-pytorch-core/ios/Image/UIImage+Resize.swift index 90c548145..2c8e42064 100644 --- a/react-native-pytorch-core/ios/Image/UIImage+Resize.swift +++ b/react-native-pytorch-core/ios/Image/UIImage+Resize.swift @@ -10,13 +10,13 @@ import UIKit extension UIImage { - func resizeImage(size: CGSize) -> UIImage? { - UIGraphicsBeginImageContextWithOptions(size, false, 1.0) - let context = UIGraphicsGetCurrentContext() - context?.interpolationQuality = .none - self.draw(in: CGRect(origin: CGPoint.zero, size: size)) - let resizedImage = UIGraphicsGetImageFromCurrentImageContext()! - UIGraphicsEndImageContext() - return resizedImage - } + func resizeImage(size: CGSize) -> UIImage? { + UIGraphicsBeginImageContextWithOptions(size, false, 1.0) + let context = UIGraphicsGetCurrentContext() + context?.interpolationQuality = .none + self.draw(in: CGRect(origin: CGPoint.zero, size: size)) + let resizedImage = UIGraphicsGetImageFromCurrentImageContext()! + UIGraphicsEndImageContext() + return resizedImage + } } diff --git a/react-native-pytorch-core/ios/JavaScript/JSContext.swift b/react-native-pytorch-core/ios/JavaScript/JSContext.swift index 9b972bb09..93abb925c 100644 --- a/react-native-pytorch-core/ios/JavaScript/JSContext.swift +++ b/react-native-pytorch-core/ios/JavaScript/JSContext.swift @@ -10,74 +10,74 @@ import Foundation @objc(PTLJSContext) public class JSContext: NSObject { - enum JSContextError: Error { - case invalidParam + enum JSContextError: Error { + case invalidParam + } + + public static let idKey = "ID" + + public static var refs: [String: NativeJSRef] = [:] + + public static func setRef(ref: NativeJSRef) -> String { + let refId = UUID().uuidString + JSContext.refs[refId] = ref + return refId + } + + public static func getRef(refId: String) throws -> NativeJSRef { + guard let unwrappedNativeJSRef = JSContext.refs[refId] else { throw JSContextError.invalidParam } + return unwrappedNativeJSRef + } + + public static func get(jsRef: [ String: String ]) throws -> NativeJSRef { + guard let refId = jsRef[idKey] else { throw JSContextError.invalidParam } + return try JSContext.getRef(refId: refId) + } + + @objc + public static func release(jsRef: [ String: String ]) throws { + guard let refId = jsRef[idKey] else { throw JSContextError.invalidParam } + let removedJSRef = refs.removeValue(forKey: refId) + try removedJSRef?.release() + } + + public static func wrapObject(object: Any) -> NativeJSRef { + return NativeJSRef(object: object) + } + + public static func unwrapObject(jsRef: [ String: String ]) throws -> Any { + guard let refId = jsRef[idKey] else { throw JSContextError.invalidParam } + let ref = try JSContext.getRef(refId: refId) + return ref.getObject() + } + + public class NativeJSRef { + // initialized mId and mJSRef to empty values to allow self to be used to set id in init() + private var mId: String? = "" + private var mObject: Any? + private var mJSRef: [String: String]? = [:] + + init(object: Any) { + mObject = object + mId = JSContext.setRef(ref: self) + mJSRef?[JSContext.idKey] = mId } - public static let idKey = "ID" - - public static var refs: [String: NativeJSRef] = [:] - - public static func setRef(ref: NativeJSRef) -> String { - let refId = UUID().uuidString - JSContext.refs[refId] = ref - return refId - } - - public static func getRef(refId: String) throws -> NativeJSRef { - guard let unwrappedNativeJSRef = JSContext.refs[refId] else { throw JSContextError.invalidParam } - return unwrappedNativeJSRef - } - - public static func get(jsRef: [ String: String ]) throws -> NativeJSRef { - guard let refId = jsRef[idKey] else { throw JSContextError.invalidParam } - return try JSContext.getRef(refId: refId) - } - - @objc - public static func release(jsRef: [ String: String ]) throws { - guard let refId = jsRef[idKey] else { throw JSContextError.invalidParam } - let removedJSRef = refs.removeValue(forKey: refId) - try removedJSRef?.release() - } - - public static func wrapObject(object: Any) -> NativeJSRef { - return NativeJSRef(object: object) + public func getJSRef() -> [String: String] { + return mJSRef ?? [:] } - public static func unwrapObject(jsRef: [ String: String ]) throws -> Any { - guard let refId = jsRef[idKey] else { throw JSContextError.invalidParam } - let ref = try JSContext.getRef(refId: refId) - return ref.getObject() + public func getObject() -> Any { + return mObject as Any } - public class NativeJSRef { - // initialized mId and mJSRef to empty values to allow self to be used to set id in init() - private var mId: String? = "" - private var mObject: Any? - private var mJSRef: [String: String]? = [:] - - init(object: Any) { - mObject = object - mId = JSContext.setRef(ref: self) - mJSRef?[JSContext.idKey] = mId - } - - public func getJSRef() -> [String: String] { - return mJSRef ?? [:] - } - - public func getObject() -> Any { - return mObject as Any - } - - public func release() throws { - if let image = mObject as? Image { - try image.close() - } - mId = nil - mObject = nil - mJSRef = nil - } + public func release() throws { + if let image = mObject as? Image { + try image.close() + } + mId = nil + mObject = nil + mJSRef = nil } + } } diff --git a/react-native-pytorch-core/ios/JavaScript/JSContextUtils.swift b/react-native-pytorch-core/ios/JavaScript/JSContextUtils.swift index 318ee3e1a..88071943c 100644 --- a/react-native-pytorch-core/ios/JavaScript/JSContextUtils.swift +++ b/react-native-pytorch-core/ios/JavaScript/JSContextUtils.swift @@ -14,14 +14,14 @@ class JSContextUtils { } public static func unwrapObject(_ objRef: String, _ objType: T.Type) throws -> T { - guard let obj = try JSContext.unwrapObject(jsRef: ["ID": objRef]) as? T else { - throw JSContextUtilsError.castingObject(objType) - } - return obj + guard let obj = try JSContext.unwrapObject(jsRef: ["ID": objRef]) as? T else { + throw JSContextUtilsError.castingObject(objType) + } + return obj } public static func unwrapObject(_ objRef: NSDictionary, _ objType: T.Type) throws -> T { - guard let ref = objRef["ID"] as? String else { throw JSContextUtilsError.castingDict(objType) } - return try unwrapObject(ref, T.self) + guard let ref = objRef["ID"] as? String else { throw JSContextUtilsError.castingDict(objType) } + return try unwrapObject(ref, T.self) } } diff --git a/react-native-pytorch-core/ios/ML/ModelLoaderModule.swift b/react-native-pytorch-core/ios/ML/ModelLoaderModule.swift index b108bccc7..88632772f 100644 --- a/react-native-pytorch-core/ios/ML/ModelLoaderModule.swift +++ b/react-native-pytorch-core/ios/ML/ModelLoaderModule.swift @@ -10,22 +10,22 @@ import Foundation @objc(ModelLoaderModule) class ModelLoaderModule: NSObject { - @objc(download:resolver:rejecter:) - public func download(_ modelUri: NSString, - resolver resolve: @escaping RCTPromiseResolveBlock, - rejecter reject: @escaping RCTPromiseRejectBlock) { - let completionHandler: (URL?, String?) -> Void = { url, error in - if let error = error { - reject(RCTErrorUnspecified, error, nil) - } else { - do { - let absolutePath = try ModelUtils.urlStringWithoutFileScheme(url: url) - resolve(absolutePath) - } catch { - reject(RCTErrorUnspecified, "download model failed", nil) - } - } + @objc(download:resolver:rejecter:) + public func download(_ modelUri: NSString, + resolver resolve: @escaping RCTPromiseResolveBlock, + rejecter reject: @escaping RCTPromiseRejectBlock) { + let completionHandler: (URL?, String?) -> Void = { url, error in + if let error = error { + reject(RCTErrorUnspecified, error, nil) + } else { + do { + let absolutePath = try ModelUtils.urlStringWithoutFileScheme(url: url) + resolve(absolutePath) + } catch { + reject(RCTErrorUnspecified, "download model failed", nil) } - ModelUtils.downloadModel(modelUri: modelUri as String, completionHandler: completionHandler) + } } + ModelUtils.downloadModel(modelUri: modelUri as String, completionHandler: completionHandler) + } } diff --git a/react-native-pytorch-core/ios/ML/ModelUtils.swift b/react-native-pytorch-core/ios/ML/ModelUtils.swift index 1933d1003..cd338420e 100644 --- a/react-native-pytorch-core/ios/ML/ModelUtils.swift +++ b/react-native-pytorch-core/ios/ML/ModelUtils.swift @@ -8,50 +8,50 @@ import Foundation enum ModelUtilsError: Error { - case downloadError + case downloadError } class ModelUtils { - static func downloadModel(modelUri: String, completionHandler: @escaping (URL?, String?) -> Void) { - if let modelUrl = URL(string: modelUri) { + static func downloadModel(modelUri: String, completionHandler: @escaping (URL?, String?) -> Void) { + if let modelUrl = URL(string: modelUri) { - // Load model from local file system if the scheme is file or if it - // doesn't have a scheme (i.e., `nil`), which means it is likely a - // local file if URI. - if modelUrl.scheme == nil || modelUrl.scheme == "file" { - completionHandler(modelUrl, nil) - } else { - let modelTask = URLSession.shared.downloadTask(with: modelUrl) { urlOrNil, _, _ in - guard let tempURL = urlOrNil else { - completionHandler(nil, "Error downloading file") - return - } - // Create path in cache directory using the last path component as a - // filename (e.g., mobilenet_v3_small.ptl). - let path = FileManager.default.urls(for: .cachesDirectory, - in: .userDomainMask)[0] - .appendingPathComponent(modelUrl.lastPathComponent) - try? FileManager.default.copyItem(at: tempURL, to: path) - completionHandler(path, nil) - } - modelTask.resume() - } - } else { - completionHandler(nil, "Could not create URLSession with provided URL") + // Load model from local file system if the scheme is file or if it + // doesn't have a scheme (i.e., `nil`), which means it is likely a + // local file if URI. + if modelUrl.scheme == nil || modelUrl.scheme == "file" { + completionHandler(modelUrl, nil) + } else { + let modelTask = URLSession.shared.downloadTask(with: modelUrl) { urlOrNil, _, _ in + guard let tempURL = urlOrNil else { + completionHandler(nil, "Error downloading file") + return + } + // Create path in cache directory using the last path component as a + // filename (e.g., mobilenet_v3_small.ptl). + let path = FileManager.default.urls(for: .cachesDirectory, + in: .userDomainMask)[0] + .appendingPathComponent(modelUrl.lastPathComponent) + try? FileManager.default.copyItem(at: tempURL, to: path) + completionHandler(path, nil) } + modelTask.resume() + } + } else { + completionHandler(nil, "Could not create URLSession with provided URL") } + } - static func urlStringWithoutFileScheme(url: URL?) throws -> String { - guard let modelUrl = url else { - throw ModelUtilsError.downloadError - } - let absolutePathString = modelUrl.absoluteString - if !modelUrl.isFileURL || modelUrl.scheme != "file" { - return absolutePathString - } - // Remove file:// from absolutePathString - let fromIdx = absolutePathString.index(absolutePathString.startIndex, offsetBy: 7) - return String(absolutePathString.suffix(from: fromIdx)) + static func urlStringWithoutFileScheme(url: URL?) throws -> String { + guard let modelUrl = url else { + throw ModelUtilsError.downloadError + } + let absolutePathString = modelUrl.absoluteString + if !modelUrl.isFileURL || modelUrl.scheme != "file" { + return absolutePathString } + // Remove file:// from absolutePathString + let fromIdx = absolutePathString.index(absolutePathString.startIndex, offsetBy: 7) + return String(absolutePathString.suffix(from: fromIdx)) + } } diff --git a/react-native-pytorch-core/ios/Media/MediaToBlob.swift b/react-native-pytorch-core/ios/Media/MediaToBlob.swift index a1045687d..147873131 100644 --- a/react-native-pytorch-core/ios/Media/MediaToBlob.swift +++ b/react-native-pytorch-core/ios/Media/MediaToBlob.swift @@ -13,7 +13,7 @@ public class MediaToBlob: NSObject { private var mBlobType: String? enum MediaToBlobError: Error { - case unknownUnwrappedObject + case unknownUnwrappedObject } // Keep blob type constants in sync with cxx/src/torchlive/media/Blob.h @@ -24,61 +24,61 @@ public class MediaToBlob: NSObject { @objc public init(refId: String) throws { - super.init() - let obj = try JSContext.unwrapObject(jsRef: ["ID": refId]) - if obj is IImage { - // swiftlint:disable:next force_cast - let image = obj as! IImage - imageToBlob(img: image) - } else if obj is IAudio { - // swiftlint:disable:next force_cast - let audio = obj as! IAudio - audioToBlob(audio: audio) - } else { - throw MediaToBlobError.unknownUnwrappedObject - } + super.init() + let obj = try JSContext.unwrapObject(jsRef: ["ID": refId]) + if obj is IImage { + // swiftlint:disable:next force_cast + let image = obj as! IImage + imageToBlob(img: image) + } else if obj is IAudio { + // swiftlint:disable:next force_cast + let audio = obj as! IAudio + audioToBlob(audio: audio) + } else { + throw MediaToBlobError.unknownUnwrappedObject + } } private func imageToBlob(img: IImage) { - let bitmap = img.getBitmap()! - let width = bitmap.width - let height = bitmap.height - let bytesPerPixel = 4 - let bytesPerRow = bytesPerPixel * width - let bitsPerComponent = 8 - var rawBytes: [UInt8] = [UInt8](repeating: 0, count: width * height * 4) - rawBytes.withUnsafeMutableBytes { ptr in - if let context = CGContext(data: ptr.baseAddress, - width: width, - height: height, - bitsPerComponent: bitsPerComponent, - bytesPerRow: bytesPerRow, - space: CGColorSpaceCreateDeviceRGB(), - bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue) { - let rect = CGRect(x: 0, y: 0, width: width, height: height) - context.draw(bitmap, in: rect) - } + let bitmap = img.getBitmap()! + let width = bitmap.width + let height = bitmap.height + let bytesPerPixel = 4 + let bytesPerRow = bytesPerPixel * width + let bitsPerComponent = 8 + var rawBytes: [UInt8] = [UInt8](repeating: 0, count: width * height * 4) + rawBytes.withUnsafeMutableBytes { ptr in + if let context = CGContext(data: ptr.baseAddress, + width: width, + height: height, + bitsPerComponent: bitsPerComponent, + bytesPerRow: bytesPerRow, + space: CGColorSpaceCreateDeviceRGB(), + bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue) { + let rect = CGRect(x: 0, y: 0, width: width, height: height) + context.draw(bitmap, in: rect) } + } - var buffer: [UInt8] = [UInt8](repeating: 0, count: width * height * 3) - for idx in 0 ..< width * height { - buffer[idx * 3 + 0] = rawBytes[idx * 4 + 0] // R - buffer[idx * 3 + 1] = rawBytes[idx * 4 + 1] // G - buffer[idx * 3 + 2] = rawBytes[idx * 4 + 2] // B - } + var buffer: [UInt8] = [UInt8](repeating: 0, count: width * height * 3) + for idx in 0 ..< width * height { + buffer[idx * 3 + 0] = rawBytes[idx * 4 + 0] // R + buffer[idx * 3 + 1] = rawBytes[idx * 4 + 1] // G + buffer[idx * 3 + 2] = rawBytes[idx * 4 + 2] // B + } - mBuffer = Data(buffer) - mBlobType = MediaToBlob.kBlobTypeImageRGB + mBuffer = Data(buffer) + mBlobType = MediaToBlob.kBlobTypeImageRGB } private func audioToBlob(audio: IAudio) { - mBuffer = audio.getData() - mBlobType = MediaToBlob.kBlobTypeAudio + mBuffer = audio.getData() + mBlobType = MediaToBlob.kBlobTypeAudio } @objc public func getByteBuffer() -> Data? { - return mBuffer + return mBuffer } @objc