replate-camera 0.1.6 → 0.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ios/ReplateCameraViewManager.swift +232 -78
- package/package.json +1 -1
|
@@ -45,7 +45,6 @@ extension UIImage {
|
|
|
45
45
|
class ReplateCameraView : UIView, ARSessionDelegate {
|
|
46
46
|
|
|
47
47
|
static var arView: ARView!
|
|
48
|
-
static var anchor: ARAnchor!
|
|
49
48
|
static var anchorEntity: AnchorEntity!
|
|
50
49
|
static var model: Entity!
|
|
51
50
|
static var spheresModels: [ModelEntity] = []
|
|
@@ -53,20 +52,27 @@ class ReplateCameraView : UIView, ARSessionDelegate {
|
|
|
53
52
|
static var lowerSpheresSet: [Bool] = [Bool](repeating: false, count: 72)
|
|
54
53
|
static var totalPhotosTaken: Int = 0
|
|
55
54
|
static var photosFromDifferentAnglesTaken = 0
|
|
55
|
+
static var INSTANCE: ReplateCameraView!
|
|
56
56
|
|
|
57
57
|
override init(frame: CGRect) {
|
|
58
58
|
super.init(frame: frame)
|
|
59
59
|
requestCameraPermissions()
|
|
60
60
|
// setupAR()
|
|
61
|
+
ReplateCameraView.INSTANCE = self
|
|
61
62
|
}
|
|
62
63
|
|
|
63
64
|
required init?(coder: NSCoder) {
|
|
64
65
|
super.init(coder: coder)
|
|
65
66
|
requestCameraPermissions()
|
|
67
|
+
ReplateCameraView.INSTANCE = self
|
|
66
68
|
// setupAR()
|
|
67
69
|
}
|
|
68
70
|
|
|
69
|
-
|
|
71
|
+
static func addRecognizer(){
|
|
72
|
+
let recognizer = UITapGestureRecognizer(target: ReplateCameraView.INSTANCE,
|
|
73
|
+
action: #selector(ReplateCameraView.INSTANCE.viewTapped(_:)))
|
|
74
|
+
ReplateCameraView.arView.addGestureRecognizer(recognizer)
|
|
75
|
+
}
|
|
70
76
|
|
|
71
77
|
func requestCameraPermissions(){
|
|
72
78
|
|
|
@@ -94,6 +100,65 @@ class ReplateCameraView : UIView, ARSessionDelegate {
|
|
|
94
100
|
print("Width: \(width), Height: \(height)")
|
|
95
101
|
self.setupAR()
|
|
96
102
|
}
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
@objc private func viewTapped(_ recognizer: UITapGestureRecognizer) {
|
|
106
|
+
print("VIEW TAPPED")
|
|
107
|
+
// guard !ReplateCameraView.arView.canBecomeFocused else {
|
|
108
|
+
// return
|
|
109
|
+
// }
|
|
110
|
+
|
|
111
|
+
let tapLocation: CGPoint = recognizer.location(in: ReplateCameraView.arView)
|
|
112
|
+
let estimatedPlane: ARRaycastQuery.Target = .estimatedPlane
|
|
113
|
+
let alignment: ARRaycastQuery.TargetAlignment = .horizontal
|
|
114
|
+
|
|
115
|
+
let result: [ARRaycastResult] = ReplateCameraView.arView.raycast(from: tapLocation,
|
|
116
|
+
allowing: estimatedPlane,
|
|
117
|
+
alignment: alignment)
|
|
118
|
+
|
|
119
|
+
guard let rayCast: ARRaycastResult = result.first
|
|
120
|
+
else { return }
|
|
121
|
+
let anchor = AnchorEntity(raycastResult: rayCast)
|
|
122
|
+
print("ANCHOR FOUND\n", anchor.transform)
|
|
123
|
+
if (ReplateCameraView.model == nil && ReplateCameraView.anchorEntity == nil){
|
|
124
|
+
ReplateCameraView.anchorEntity = anchor
|
|
125
|
+
let anchorTransform = anchor.transform
|
|
126
|
+
// let path = Bundle.main.path(forResource: "anchor", ofType: "usdz")!
|
|
127
|
+
// let url = URL(fileURLWithPath: path)
|
|
128
|
+
// let entity: ModelEntity = try! ModelEntity.loadModel(contentsOf: url)
|
|
129
|
+
|
|
130
|
+
// if #available(iOS 15.0, *) {
|
|
131
|
+
// entity.model!.mesh.
|
|
132
|
+
// ReplateCameraView.spheresModels = Array(entity.model!.mesh.contents.models)
|
|
133
|
+
// }
|
|
134
|
+
// entity.scale *= 4.5
|
|
135
|
+
// entity.position = SIMD3(anchorTransform.columns.3.x, anchorTransform.columns.3.y, anchorTransform.columns.3.z)
|
|
136
|
+
|
|
137
|
+
func createSphere(position: SIMD3<Float>) -> ModelEntity {
|
|
138
|
+
let sphereMesh = MeshResource.generateSphere(radius: 0.0025)
|
|
139
|
+
let sphereEntity = ModelEntity(mesh: sphereMesh, materials: [SimpleMaterial(color: .white.withAlphaComponent(0.7), isMetallic: false)])
|
|
140
|
+
sphereEntity.position = position
|
|
141
|
+
return sphereEntity
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
func createSpheres(y: Float){
|
|
145
|
+
let radius = Float(0.1)
|
|
146
|
+
for i in 0..<72 {
|
|
147
|
+
let angle = Float(i) * (Float.pi / 180) * 5 // 10 degrees in radians
|
|
148
|
+
let x = radius * cos(angle)
|
|
149
|
+
let z = radius * sin(angle)
|
|
150
|
+
let spherePosition = SIMD3<Float>(x, y, z)
|
|
151
|
+
let sphereEntity = createSphere(position: spherePosition)
|
|
152
|
+
ReplateCameraView.spheresModels.append(sphereEntity)
|
|
153
|
+
ReplateCameraView.anchorEntity.addChild(sphereEntity)
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
createSpheres(y: 0.0)
|
|
158
|
+
createSpheres(y: 0.3)
|
|
159
|
+
ReplateCameraView.arView.scene.anchors.append(ReplateCameraView.anchorEntity)
|
|
160
|
+
}
|
|
161
|
+
}
|
|
97
162
|
|
|
98
163
|
func setupAR() {
|
|
99
164
|
print("Setup AR")
|
|
@@ -109,6 +174,10 @@ class ReplateCameraView : UIView, ARSessionDelegate {
|
|
|
109
174
|
// else { fatalError("See no reference object") }
|
|
110
175
|
// print(obj)
|
|
111
176
|
configuration.planeDetection = ARWorldTrackingConfiguration.PlaneDetection.horizontal
|
|
177
|
+
// ReplateCameraView.arView.debugOptions = [
|
|
178
|
+
// .showAnchorOrigins,
|
|
179
|
+
// .showAnchorGeometry
|
|
180
|
+
// ]
|
|
112
181
|
if #available(iOS 16.0, *) {
|
|
113
182
|
print("recommendedVideoFormatForHighResolutionFrameCapturing")
|
|
114
183
|
configuration.videoFormat = ARWorldTrackingConfiguration.recommendedVideoFormatForHighResolutionFrameCapturing ?? ARWorldTrackingConfiguration.recommendedVideoFormatFor4KResolution ?? ARWorldTrackingConfiguration.supportedVideoFormats[0]
|
|
@@ -123,61 +192,14 @@ class ReplateCameraView : UIView, ARSessionDelegate {
|
|
|
123
192
|
}
|
|
124
193
|
// configuration.detectionObjects = obj
|
|
125
194
|
ReplateCameraView.arView.session.run(configuration)
|
|
195
|
+
|
|
196
|
+
ReplateCameraView.arView.addCoaching()
|
|
126
197
|
}
|
|
127
198
|
|
|
128
199
|
func session(_ session: ARSession, didAdd anchors: [ARAnchor]) {
|
|
129
|
-
|
|
130
|
-
if (ReplateCameraView.anchor == nil){
|
|
131
|
-
guard let _anchor = anchors.first else { return }
|
|
132
|
-
ReplateCameraView.anchor = _anchor
|
|
133
|
-
}
|
|
134
|
-
if (ReplateCameraView.model == nil && ReplateCameraView.anchorEntity == nil){
|
|
135
|
-
let anchorTransform = ReplateCameraView.anchor.transform
|
|
136
|
-
// let path = Bundle.main.path(forResource: "anchor", ofType: "usdz")!
|
|
137
|
-
// let url = URL(fileURLWithPath: path)
|
|
138
|
-
// let entity: ModelEntity = try! ModelEntity.loadModel(contentsOf: url)
|
|
139
|
-
|
|
140
|
-
// if #available(iOS 15.0, *) {
|
|
141
|
-
// entity.model!.mesh.
|
|
142
|
-
// ReplateCameraView.spheresModels = Array(entity.model!.mesh.contents.models)
|
|
143
|
-
// }
|
|
144
|
-
// entity.scale *= 4.5
|
|
145
|
-
// entity.position = SIMD3(anchorTransform.columns.3.x, anchorTransform.columns.3.y, anchorTransform.columns.3.z)
|
|
146
|
-
|
|
147
|
-
func createSphere(position: SIMD3<Float>) -> ModelEntity {
|
|
148
|
-
let sphereMesh = MeshResource.generateSphere(radius: 0.0025)
|
|
149
|
-
let sphereEntity = ModelEntity(mesh: sphereMesh, materials: [SimpleMaterial(color: .white.withAlphaComponent(0.7), isMetallic: false)])
|
|
150
|
-
sphereEntity.position = position
|
|
151
|
-
return sphereEntity
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
func createSpheres(y: Float){
|
|
155
|
-
let radius = Float(0.1)
|
|
156
|
-
for i in 0..<72 {
|
|
157
|
-
let angle = Float(i) * (Float.pi / 180) * 5 // 10 degrees in radians
|
|
158
|
-
let x = radius * cos(angle)
|
|
159
|
-
let z = radius * sin(angle)
|
|
160
|
-
let spherePosition = SIMD3<Float>(x, y, z)
|
|
161
|
-
let sphereEntity = createSphere(position: spherePosition)
|
|
162
|
-
ReplateCameraView.spheresModels.append(sphereEntity)
|
|
163
|
-
ReplateCameraView.anchorEntity.addChild(sphereEntity)
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
ReplateCameraView.anchorEntity = AnchorEntity()
|
|
168
|
-
createSpheres(y: 0.0)
|
|
169
|
-
createSpheres(y: 0.3)
|
|
170
|
-
ReplateCameraView.arView.scene.anchors.append(ReplateCameraView.anchorEntity)
|
|
171
|
-
}
|
|
200
|
+
|
|
172
201
|
}
|
|
173
202
|
|
|
174
|
-
// @objc func setCameraRect(_ node: NSNumber, rect: NSDictionary) {
|
|
175
|
-
// let x = rect["x"] as? CGFloat ?? 0
|
|
176
|
-
// let y = rect["y"] as? CGFloat ?? 0
|
|
177
|
-
// let width = rect["width"] as? CGFloat ?? 0
|
|
178
|
-
// let height = rect["height"] as? CGFloat ?? 0
|
|
179
|
-
//// arView.(CGRect(x: x, y: y, width: width, height: height))
|
|
180
|
-
// }
|
|
181
203
|
|
|
182
204
|
@objc var color: String = "" {
|
|
183
205
|
didSet {
|
|
@@ -220,11 +242,17 @@ class ReplateCameraView : UIView, ARSessionDelegate {
|
|
|
220
242
|
ReplateCameraView.arView.session.run(ARWorldTrackingConfiguration())
|
|
221
243
|
}
|
|
222
244
|
|
|
223
|
-
|
|
224
245
|
}
|
|
225
246
|
|
|
226
247
|
@objc(ReplateCameraController)
|
|
227
|
-
class ReplateCameraController:
|
|
248
|
+
class ReplateCameraController: RCTEventEmitter {
|
|
249
|
+
|
|
250
|
+
static var INSTANCE: ReplateCameraController!
|
|
251
|
+
|
|
252
|
+
override init() {
|
|
253
|
+
super.init()
|
|
254
|
+
ReplateCameraController.INSTANCE = self
|
|
255
|
+
}
|
|
228
256
|
|
|
229
257
|
@objc(getPhotosCount:rejecter:)
|
|
230
258
|
func getPhotosCount(_ resolver: RCTPromiseResolveBlock, rejecter: RCTPromiseRejectBlock) -> Void{
|
|
@@ -246,17 +274,17 @@ class ReplateCameraController: NSObject {
|
|
|
246
274
|
|
|
247
275
|
//DEVICE ORIENTATION
|
|
248
276
|
guard let anchorNode = ReplateCameraView.anchorEntity else {
|
|
249
|
-
rejecter("[ReplateCameraController]", "
|
|
277
|
+
rejecter("[ReplateCameraController]", "No anchor set yet", NSError(domain: "ReplateCameraController", code: 001, userInfo: nil));
|
|
250
278
|
return
|
|
251
279
|
}
|
|
252
280
|
|
|
253
281
|
// Assuming you have two points
|
|
254
|
-
let point1 = SIMD3<Float>(anchorNode.position.x,
|
|
255
|
-
anchorNode.position.y,
|
|
256
|
-
anchorNode.position.z)
|
|
257
|
-
let point2 = SIMD3<Float>(anchorNode.position.x,
|
|
258
|
-
anchorNode.position.y + 0.3,
|
|
259
|
-
anchorNode.position.z)
|
|
282
|
+
let point1 = SIMD3<Float>(anchorNode.position(relativeTo: nil).x,
|
|
283
|
+
anchorNode.position(relativeTo: nil).y,
|
|
284
|
+
anchorNode.position(relativeTo: nil).z)
|
|
285
|
+
let point2 = SIMD3<Float>(anchorNode.position(relativeTo: nil).x,
|
|
286
|
+
anchorNode.position(relativeTo: nil).y + 0.3,
|
|
287
|
+
anchorNode.position(relativeTo: nil).z)
|
|
260
288
|
|
|
261
289
|
// Function to calculate the angle between two vectors
|
|
262
290
|
func angleBetweenVectors(_ vector1: SIMD3<Float>, _ vector2: SIMD3<Float>) -> Float {
|
|
@@ -265,22 +293,33 @@ class ReplateCameraController: NSObject {
|
|
|
265
293
|
}
|
|
266
294
|
|
|
267
295
|
// Threshold angle for considering if the device is pointing towards a point
|
|
268
|
-
let thresholdAngle: Float = 0.3 // Adjust this threshold as needed
|
|
269
296
|
var deviceTargetInFocus = -1
|
|
270
297
|
// Check if the device is pointing towards one of the two points
|
|
271
298
|
if let cameraTransform = ReplateCameraView.arView.session.currentFrame?.camera.transform {
|
|
272
299
|
let deviceDirection = SIMD3<Float>(-cameraTransform.columns.2.x, -cameraTransform.columns.2.y, -cameraTransform.columns.2.z)
|
|
273
300
|
|
|
274
301
|
let cameraPosition = SIMD3<Float>(cameraTransform.columns.3.x, cameraTransform.columns.3.y, cameraTransform.columns.3.z)
|
|
275
|
-
|
|
276
302
|
let directionToFirstPoint = normalize(point1 - cameraPosition)
|
|
277
303
|
let directionToSecondPoint = normalize(point2 - cameraPosition)
|
|
278
304
|
|
|
305
|
+
let point1Distance = distance(point1, cameraPosition)
|
|
306
|
+
let point2Distance = distance(point2, cameraPosition)
|
|
307
|
+
let averageDistance = (point1Distance + point2Distance) / 2.0
|
|
308
|
+
|
|
309
|
+
let baseThreshold: Float = 0.6 // adjust as needed
|
|
310
|
+
let distanceFactor: Float = 0.2 // adjust as needed
|
|
311
|
+
|
|
312
|
+
let dynamicThreshold = baseThreshold + distanceFactor * sqrt(averageDistance)
|
|
313
|
+
|
|
314
|
+
|
|
279
315
|
let angleToFirstPoint = angleBetweenVectors(deviceDirection, directionToFirstPoint)
|
|
280
316
|
let angleToSecondPoint = angleBetweenVectors(deviceDirection, directionToSecondPoint)
|
|
281
|
-
print("Camera
|
|
282
|
-
|
|
283
|
-
|
|
317
|
+
print("Camera: \(cameraPosition)")
|
|
318
|
+
print("Point 1 position: \(point1) Point 2 position: \(point2)")
|
|
319
|
+
print("Angle to first: ", angleToFirstPoint, " Angle to second: ", angleToSecondPoint)
|
|
320
|
+
print("Threshold \(dynamicThreshold)")
|
|
321
|
+
let isPointingAtFirstPoint = angleToFirstPoint < dynamicThreshold && cameraPosition.y < anchorNode.position.y + 0.20
|
|
322
|
+
let isPointingAtSecondPoint = angleToSecondPoint < dynamicThreshold && cameraPosition.y >= anchorNode.position.y + 0.20
|
|
284
323
|
if (isPointingAtFirstPoint) {
|
|
285
324
|
deviceTargetInFocus = 0
|
|
286
325
|
}else if(isPointingAtSecondPoint){
|
|
@@ -304,6 +343,18 @@ class ReplateCameraController: NSObject {
|
|
|
304
343
|
let uiImage = UIImage(cgImage: cgImage)
|
|
305
344
|
let finImage = uiImage.rotate(radians: .pi/2) // Adjust radians as needed
|
|
306
345
|
|
|
346
|
+
guard let components = finImage.averageColor()?.getRGBComponents()
|
|
347
|
+
else {
|
|
348
|
+
rejecter("[ReplateCameraController]", "Cannot get color components", NSError(domain: "ReplateCameraController", code: 003, userInfo: nil))
|
|
349
|
+
return
|
|
350
|
+
}
|
|
351
|
+
let averagePixelColor = components.red + components.blue + components.green / 3
|
|
352
|
+
print("Average pixel color: \(averagePixelColor)")
|
|
353
|
+
if ((components.red + components.blue + components.green) / 3 < 0.15){
|
|
354
|
+
rejecter("[ReplateCameraController]", "Image too dark", NSError(domain: "ReplateCameraController", code: 004, userInfo: nil))
|
|
355
|
+
return
|
|
356
|
+
}
|
|
357
|
+
|
|
307
358
|
print("Saving photo")
|
|
308
359
|
if let url = ReplateCameraController.saveImageAsJPEG(finImage) {
|
|
309
360
|
resolver(url.absoluteString)
|
|
@@ -330,7 +381,6 @@ class ReplateCameraController: NSObject {
|
|
|
330
381
|
}
|
|
331
382
|
|
|
332
383
|
|
|
333
|
-
|
|
334
384
|
static func saveImageAsJPEG(_ image: UIImage) -> URL? {
|
|
335
385
|
// Convert UIImage to Data with JPEG representation
|
|
336
386
|
guard let imageData = image.jpegData(compressionQuality: 1.0) else {
|
|
@@ -363,12 +413,17 @@ class ReplateCameraController: NSObject {
|
|
|
363
413
|
return nil
|
|
364
414
|
}
|
|
365
415
|
}
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
416
|
+
|
|
417
|
+
@objc
|
|
418
|
+
func sendTutorialEndedEvent(){
|
|
419
|
+
self.sendEvent(withName: "arTutorialCompleted", body: true)
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
@objc
|
|
423
|
+
override func supportedEvents() -> [String]! {
|
|
424
|
+
return ["arTutorialCompleted"]
|
|
425
|
+
}
|
|
426
|
+
|
|
372
427
|
|
|
373
428
|
func updateSpheres(deviceTargetInFocus: Int) {
|
|
374
429
|
guard let anchorNode = ReplateCameraView.anchorEntity else { return }
|
|
@@ -378,9 +433,9 @@ class ReplateCameraController: NSObject {
|
|
|
378
433
|
let cameraTransform = frame.camera.transform
|
|
379
434
|
|
|
380
435
|
// Calculate the angle between the camera and the anchor
|
|
381
|
-
let anchorPosition = SCNVector3(anchorNode.position.x,
|
|
382
|
-
anchorNode.position.y,
|
|
383
|
-
anchorNode.position.z)
|
|
436
|
+
let anchorPosition = SCNVector3(anchorNode.position(relativeTo: nil).x,
|
|
437
|
+
anchorNode.position(relativeTo: nil).y,
|
|
438
|
+
anchorNode.position(relativeTo: nil).z)
|
|
384
439
|
let cameraPosition = SCNVector3(cameraTransform.columns.3.x,
|
|
385
440
|
cameraTransform.columns.3.y,
|
|
386
441
|
cameraTransform.columns.3.z)
|
|
@@ -409,9 +464,9 @@ class ReplateCameraController: NSObject {
|
|
|
409
464
|
}
|
|
410
465
|
}
|
|
411
466
|
|
|
412
|
-
func calculateAngle(_
|
|
467
|
+
func calculateAngle(_ camera: SCNVector3, _ anchor: SCNVector3) -> Float {
|
|
413
468
|
// Calculate the angle in 2D plane (x-z plane) using atan2
|
|
414
|
-
let angle = atan2(
|
|
469
|
+
let angle = atan2(camera.z - anchor.z, camera.x - anchor.x)
|
|
415
470
|
|
|
416
471
|
// Convert from radians to degrees
|
|
417
472
|
var angleInDegrees = GLKMathRadiansToDegrees(Float(angle))
|
|
@@ -421,6 +476,14 @@ class ReplateCameraController: NSObject {
|
|
|
421
476
|
angleInDegrees += 360
|
|
422
477
|
}
|
|
423
478
|
|
|
479
|
+
// Rotate by 2.5 degrees to consider sphere-center, not sphere-begin
|
|
480
|
+
angleInDegrees += 2.5
|
|
481
|
+
|
|
482
|
+
// If rotating resulted in angle > 360 degrees, subtract 360
|
|
483
|
+
if angleInDegrees >= 360 {
|
|
484
|
+
angleInDegrees -= 360
|
|
485
|
+
}
|
|
486
|
+
|
|
424
487
|
return angleInDegrees
|
|
425
488
|
}
|
|
426
489
|
|
|
@@ -436,3 +499,94 @@ extension SCNVector3 {
|
|
|
436
499
|
return a.x*b.x + a.y*b.y + a.z*b.z
|
|
437
500
|
}
|
|
438
501
|
}
|
|
502
|
+
|
|
503
|
+
extension ARView: ARCoachingOverlayViewDelegate {
|
|
504
|
+
func addCoaching() {
|
|
505
|
+
print("ADD COACHING")
|
|
506
|
+
// Create a ARCoachingOverlayView object
|
|
507
|
+
let coachingOverlay = ARCoachingOverlayView()
|
|
508
|
+
// Make sure it rescales if the device orientation changes
|
|
509
|
+
coachingOverlay.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
|
510
|
+
self.addSubview(coachingOverlay)
|
|
511
|
+
coachingOverlay.center = self.convert(self.center, from:self.superview)
|
|
512
|
+
// Set the Augmented Reality goal
|
|
513
|
+
coachingOverlay.goal = .horizontalPlane
|
|
514
|
+
// Set the ARSession
|
|
515
|
+
coachingOverlay.session = self.session
|
|
516
|
+
// Set the delegate for any callbacks
|
|
517
|
+
coachingOverlay.delegate = self
|
|
518
|
+
coachingOverlay.setActive(true, animated: true)
|
|
519
|
+
}
|
|
520
|
+
// Example callback for the delegate object
|
|
521
|
+
public func coachingOverlayViewDidDeactivate(
|
|
522
|
+
_ coachingOverlayView: ARCoachingOverlayView
|
|
523
|
+
) {
|
|
524
|
+
print("DEACTIVATED")
|
|
525
|
+
ReplateCameraController.INSTANCE.sendTutorialEndedEvent()
|
|
526
|
+
ReplateCameraView.addRecognizer()
|
|
527
|
+
print("CRASHED")
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
extension UIImage {
|
|
532
|
+
func averageColor() -> UIColor? {
|
|
533
|
+
// Convert UIImage to CGImage
|
|
534
|
+
guard let cgImage = self.cgImage else { return nil }
|
|
535
|
+
|
|
536
|
+
// Get width and height of the image
|
|
537
|
+
let width = cgImage.width
|
|
538
|
+
let height = cgImage.height
|
|
539
|
+
|
|
540
|
+
// Create a data provider from CGImage
|
|
541
|
+
guard let dataProvider = cgImage.dataProvider else { return nil }
|
|
542
|
+
|
|
543
|
+
// Access pixel data
|
|
544
|
+
guard let pixelData = dataProvider.data else { return nil }
|
|
545
|
+
|
|
546
|
+
// Create a pointer to the pixel data
|
|
547
|
+
let data: UnsafePointer<UInt8> = CFDataGetBytePtr(pixelData)
|
|
548
|
+
|
|
549
|
+
var totalRed: CGFloat = 0
|
|
550
|
+
var totalGreen: CGFloat = 0
|
|
551
|
+
var totalBlue: CGFloat = 0
|
|
552
|
+
|
|
553
|
+
// Loop through each pixel and calculate sum of RGB values
|
|
554
|
+
for y in 0..<height {
|
|
555
|
+
for x in 0..<width {
|
|
556
|
+
let pixelInfo: Int = ((width * y) + x) * 4
|
|
557
|
+
let red = CGFloat(data[pixelInfo]) / 255.0
|
|
558
|
+
let green = CGFloat(data[pixelInfo + 1]) / 255.0
|
|
559
|
+
let blue = CGFloat(data[pixelInfo + 2]) / 255.0
|
|
560
|
+
|
|
561
|
+
totalRed += red
|
|
562
|
+
totalGreen += green
|
|
563
|
+
totalBlue += blue
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
// Calculate average RGB values
|
|
568
|
+
let count = CGFloat(width * height)
|
|
569
|
+
let averageRed = totalRed / count
|
|
570
|
+
let averageGreen = totalGreen / count
|
|
571
|
+
let averageBlue = totalBlue / count
|
|
572
|
+
|
|
573
|
+
// Create and return average color
|
|
574
|
+
return UIColor(red: averageRed, green: averageGreen, blue: averageBlue, alpha: 1.0)
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
extension UIColor {
|
|
579
|
+
func getRGBComponents() -> (red: CGFloat, green: CGFloat, blue: CGFloat)? {
|
|
580
|
+
var red: CGFloat = 0
|
|
581
|
+
var green: CGFloat = 0
|
|
582
|
+
var blue: CGFloat = 0
|
|
583
|
+
var alpha: CGFloat = 0
|
|
584
|
+
|
|
585
|
+
// Check if the color can be converted to RGB
|
|
586
|
+
guard self.getRed(&red, green: &green, blue: &blue, alpha: &alpha) else {
|
|
587
|
+
return nil
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
return (red, green, blue)
|
|
591
|
+
}
|
|
592
|
+
}
|