Skip to content

Commit

Permalink
Hackday: show a message when the picture will most likely appear too …
Browse files Browse the repository at this point in the history
…dark (#38)

* Show a message when the picture is too dark

* Add low light icon

* Refactor localizable strings

* Update podspec to include resources

* Remove comments
  • Loading branch information
vadymmarkov authored May 29, 2019
1 parent 204d8e6 commit 1173ab1
Show file tree
Hide file tree
Showing 14 changed files with 329 additions and 41 deletions.
16 changes: 0 additions & 16 deletions Demo/nb.lproj/Localizable.strings

This file was deleted.

6 changes: 3 additions & 3 deletions Finjinon.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@ Finjinon is a custom AVFoundation based camera UI, focused on quickly adding sev
s.platform = :ios, '10.0'
s.swift_version = '4.2'
s.source = { :git => "https://github.com/finn-no/Finjinon.git", :tag => s.version }
s.resources = 'Sources/Resources/*.xcassets'
s.resources = 'Sources/Resources/*.{xcassets,lproj}'
s.resource_bundles = {
'Finjinon' => ['Sources/Resources/*.xcassets']
'Finjinon' => ['Sources/Resources/*.xcassets', 'Sources/Resources/*.lproj']
}
s.requires_arc = true
s.source_files = "Sources/*.swift"
s.source_files = "Sources/**/*.swift"
s.frameworks = "Foundation"
end
48 changes: 37 additions & 11 deletions Finjinon.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
CF9B327D229829AD00CCB8F9 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF9B3271229829AD00CCB8F9 /* ViewController.swift */; };
CF9B327E229829AD00CCB8F9 /* LaunchScreen.strings in Resources */ = {isa = PBXBuildFile; fileRef = CF9B3272229829AD00CCB8F9 /* LaunchScreen.strings */; };
CF9B327F229829AD00CCB8F9 /* Main.strings in Resources */ = {isa = PBXBuildFile; fileRef = CF9B3274229829AD00CCB8F9 /* Main.strings */; };
CF9B3280229829AD00CCB8F9 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = CF9B3276229829AD00CCB8F9 /* Localizable.strings */; };
CF9B3281229829AD00CCB8F9 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = CF9B3278229829AD00CCB8F9 /* Assets.xcassets */; };
CF9B3282229829AD00CCB8F9 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = CF9B3279229829AD00CCB8F9 /* LaunchScreen.storyboard */; };
CF9B3283229829AD00CCB8F9 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF9B327B229829AD00CCB8F9 /* AppDelegate.swift */; };
Expand All @@ -28,6 +27,11 @@
CF9B3299229829E900CCB8F9 /* PhotoCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF9B328E229829E900CCB8F9 /* PhotoCollectionViewCell.swift */; };
CF9B329A229829E900CCB8F9 /* PhotoStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF9B328F229829E900CCB8F9 /* PhotoStorage.swift */; };
CF9B329F229829FC00CCB8F9 /* FinjinonTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF9B329D229829FC00CCB8F9 /* FinjinonTests.swift */; };
CFBD7133229D6499003B1BD9 /* LightingCondition.swift in Sources */ = {isa = PBXBuildFile; fileRef = CFBD7131229D6499003B1BD9 /* LightingCondition.swift */; };
CFBD7134229D6499003B1BD9 /* LowLightService.swift in Sources */ = {isa = PBXBuildFile; fileRef = CFBD7132229D6499003B1BD9 /* LowLightService.swift */; };
CFBD7138229D66C0003B1BD9 /* StringExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = CFBD7137229D66C0003B1BD9 /* StringExtensions.swift */; };
CFBD7139229D6876003B1BD9 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = CFBD713B229D6876003B1BD9 /* Localizable.strings */; };
CFBD713E229D68FF003B1BD9 /* LowLightView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CFBD713D229D68FF003B1BD9 /* LowLightView.swift */; };
/* End PBXBuildFile section */

/* Begin PBXContainerItemProxy section */
Expand Down Expand Up @@ -75,7 +79,6 @@
CF9B3271229829AD00CCB8F9 /* ViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = "<group>"; };
CF9B3273229829AD00CCB8F9 /* nb */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nb; path = nb.lproj/LaunchScreen.strings; sourceTree = "<group>"; };
CF9B3275229829AD00CCB8F9 /* nb */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nb; path = nb.lproj/Main.strings; sourceTree = "<group>"; };
CF9B3277229829AD00CCB8F9 /* nb */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nb; path = nb.lproj/Localizable.strings; sourceTree = "<group>"; };
CF9B3278229829AD00CCB8F9 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
CF9B327A229829AD00CCB8F9 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
CF9B327B229829AD00CCB8F9 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
Expand All @@ -93,6 +96,12 @@
CF9B3290229829E900CCB8F9 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
CF9B329D229829FC00CCB8F9 /* FinjinonTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FinjinonTests.swift; sourceTree = "<group>"; };
CF9B329E229829FC00CCB8F9 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
CFBD7131229D6499003B1BD9 /* LightingCondition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LightingCondition.swift; sourceTree = "<group>"; };
CFBD7132229D6499003B1BD9 /* LowLightService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LowLightService.swift; sourceTree = "<group>"; };
CFBD7137229D66C0003B1BD9 /* StringExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StringExtensions.swift; sourceTree = "<group>"; };
CFBD713A229D6876003B1BD9 /* nb */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nb; path = nb.lproj/Localizable.strings; sourceTree = "<group>"; };
CFBD713C229D687C003B1BD9 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = "<group>"; };
CFBD713D229D68FF003B1BD9 /* LowLightView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LowLightView.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
Expand Down Expand Up @@ -146,6 +155,7 @@
isa = PBXGroup;
children = (
CF9B3288229829E900CCB8F9 /* Assets.xcassets */,
CFBD713B229D6876003B1BD9 /* Localizable.strings */,
);
path = Resources;
sourceTree = "<group>";
Expand All @@ -156,7 +166,6 @@
CF9B3271229829AD00CCB8F9 /* ViewController.swift */,
CF9B3272229829AD00CCB8F9 /* LaunchScreen.strings */,
CF9B3274229829AD00CCB8F9 /* Main.strings */,
CF9B3276229829AD00CCB8F9 /* Localizable.strings */,
CF9B3278229829AD00CCB8F9 /* Assets.xcassets */,
CF9B3279229829AD00CCB8F9 /* LaunchScreen.storyboard */,
CF9B327B229829AD00CCB8F9 /* AppDelegate.swift */,
Expand All @@ -168,17 +177,19 @@
CF9B3285229829E900CCB8F9 /* Sources */ = {
isa = PBXGroup;
children = (
CF414C26229A9A91008980E0 /* Resources */,
CF9B328A229829E900CCB8F9 /* Finjinon.h */,
CF9B3290229829E900CCB8F9 /* Info.plist */,
CF9B328C229829E900CCB8F9 /* AssetResolver.swift */,
CF9B3287229829E900CCB8F9 /* Buttons.swift */,
CF9B3289229829E900CCB8F9 /* CaptureManager.swift */,
CF9B328D229829E900CCB8F9 /* ImagePickerAdapter.swift */,
CFBD713F229D690C003B1BD9 /* LowLight */,
CF9B328B229829E900CCB8F9 /* PhotoCaptureViewController.swift */,
CF9B328E229829E900CCB8F9 /* PhotoCollectionViewCell.swift */,
CF9B3286229829E900CCB8F9 /* PhotoCollectionViewLayout.swift */,
CF9B328F229829E900CCB8F9 /* PhotoStorage.swift */,
CFBD7137229D66C0003B1BD9 /* StringExtensions.swift */,
CF414C26229A9A91008980E0 /* Resources */,
);
path = Sources;
sourceTree = "<group>";
Expand All @@ -192,6 +203,16 @@
path = Tests;
sourceTree = "<group>";
};
CFBD713F229D690C003B1BD9 /* LowLight */ = {
isa = PBXGroup;
children = (
CFBD7132229D6499003B1BD9 /* LowLightService.swift */,
CFBD713D229D68FF003B1BD9 /* LowLightView.swift */,
CFBD7131229D6499003B1BD9 /* LightingCondition.swift */,
);
path = LowLight;
sourceTree = "<group>";
};
/* End PBXGroup section */

/* Begin PBXHeadersBuildPhase section */
Expand Down Expand Up @@ -316,7 +337,6 @@
buildActionMask = 2147483647;
files = (
CF9B327E229829AD00CCB8F9 /* LaunchScreen.strings in Resources */,
CF9B3280229829AD00CCB8F9 /* Localizable.strings in Resources */,
CF9B3281229829AD00CCB8F9 /* Assets.xcassets in Resources */,
CF9B3282229829AD00CCB8F9 /* LaunchScreen.storyboard in Resources */,
CF9B327F229829AD00CCB8F9 /* Main.strings in Resources */,
Expand All @@ -328,6 +348,7 @@
buildActionMask = 2147483647;
files = (
CF9B3293229829E900CCB8F9 /* Assets.xcassets in Resources */,
CFBD7139229D6876003B1BD9 /* Localizable.strings in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down Expand Up @@ -357,10 +378,14 @@
CF9B3296229829E900CCB8F9 /* PhotoCaptureViewController.swift in Sources */,
CF9B3291229829E900CCB8F9 /* PhotoCollectionViewLayout.swift in Sources */,
CF9B3292229829E900CCB8F9 /* Buttons.swift in Sources */,
CFBD7134229D6499003B1BD9 /* LowLightService.swift in Sources */,
CF9B3294229829E900CCB8F9 /* CaptureManager.swift in Sources */,
CF9B329A229829E900CCB8F9 /* PhotoStorage.swift in Sources */,
CFBD713E229D68FF003B1BD9 /* LowLightView.swift in Sources */,
CFBD7138229D66C0003B1BD9 /* StringExtensions.swift in Sources */,
CF9B3299229829E900CCB8F9 /* PhotoCollectionViewCell.swift in Sources */,
CF9B3297229829E900CCB8F9 /* AssetResolver.swift in Sources */,
CFBD7133229D6499003B1BD9 /* LightingCondition.swift in Sources */,
CF9B3298229829E900CCB8F9 /* ImagePickerAdapter.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down Expand Up @@ -410,20 +435,21 @@
name = Main.strings;
sourceTree = "<group>";
};
CF9B3276229829AD00CCB8F9 /* Localizable.strings */ = {
CF9B3279229829AD00CCB8F9 /* LaunchScreen.storyboard */ = {
isa = PBXVariantGroup;
children = (
CF9B3277229829AD00CCB8F9 /* nb */,
CF9B327A229829AD00CCB8F9 /* Base */,
);
name = Localizable.strings;
name = LaunchScreen.storyboard;
sourceTree = "<group>";
};
CF9B3279229829AD00CCB8F9 /* LaunchScreen.storyboard */ = {
CFBD713B229D6876003B1BD9 /* Localizable.strings */ = {
isa = PBXVariantGroup;
children = (
CF9B327A229829AD00CCB8F9 /* Base */,
CFBD713A229D6876003B1BD9 /* nb */,
CFBD713C229D687C003B1BD9 /* en */,
);
name = LaunchScreen.storyboard;
name = Localizable.strings;
sourceTree = "<group>";
};
/* End PBXVariantGroup section */
Expand Down
49 changes: 48 additions & 1 deletion Sources/CaptureManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,14 @@ enum CaptureManagerViewfinderMode {
case window
}

protocol CaptureManagerDelegate: AnyObject {
func captureManager(_ manager: CaptureManager, didDetectLightingCondition: LightingCondition)
}


class CaptureManager: NSObject {
weak var delegate: CaptureManagerDelegate?

let previewLayer: AVCaptureVideoPreviewLayer
var flashMode: AVCaptureDevice.FlashMode {
return cameraDevice?.flashMode ?? .off
Expand All @@ -37,6 +44,10 @@ class CaptureManager: NSObject {
fileprivate var cameraDevice: AVCaptureDevice?
fileprivate var stillImageOutput: AVCaptureStillImageOutput?
fileprivate var orientation = AVCaptureVideoOrientation.portrait
private var lastVideoCaptureTime = CMTime()
private let lowLightService = LowLightService()

/// Array of vision requests

override init() {
session.sessionPreset = AVCaptureSession.Preset.photo
Expand Down Expand Up @@ -169,7 +180,7 @@ class CaptureManager: NSObject {
// MARK: - Private methods

fileprivate func accessDeniedError(_ code: Int = FinjinonCameraAccessErrorDeniedCode) -> NSError {
let info = [NSLocalizedDescriptionKey: NSLocalizedString("Camera access denied, please enable it in the Settings app to continue", comment: "")]
let info = [NSLocalizedDescriptionKey: "finjinon.cameraAccessDenied".localized()]
return NSError(domain: FinjinonCameraAccessErrorDomain, code: code, userInfo: info)
}

Expand Down Expand Up @@ -218,6 +229,12 @@ class CaptureManager: NSObject {
self.session.addOutput(self.stillImageOutput!)
}

let videoOutput = self.makeVideoDataOutput()

if self.session.canAddOutput(videoOutput) {
self.session.addOutput(videoOutput)
}

if let cameraDevice = self.cameraDevice {
if cameraDevice.isFocusModeSupported(.continuousAutoFocus) {
do {
Expand Down Expand Up @@ -251,4 +268,34 @@ class CaptureManager: NSObject {

return AVCaptureDevice.default(for: AVMediaType.video)
}

private func makeVideoDataOutput() -> AVCaptureVideoDataOutput {
let output = AVCaptureVideoDataOutput()
output.videoSettings = [kCVPixelBufferPixelFormatTypeKey as String: kCVPixelFormatType_32BGRA]
output.alwaysDiscardsLateVideoFrames = true
output.setSampleBufferDelegate(self, queue: DispatchQueue(label: "no.finn.finjinon-sample-buffer"))
return output
}
}

// MARK: - AVCaptureVideoDataOutputSampleBufferDelegate

extension CaptureManager: AVCaptureVideoDataOutputSampleBufferDelegate {
func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
let time = CMSampleBufferGetPresentationTimeStamp(sampleBuffer)
let fps: Int32 = 1 // Create pixel buffer and call the delegate 1 time per second

guard (time - lastVideoCaptureTime) >= CMTime.init(value: 1, timescale: fps) else {
return
}

lastVideoCaptureTime = time

if let lightningCondition = lowLightService.getLightningCondition(from: sampleBuffer) {
DispatchQueue.main.async { [weak self] in
guard let self = self else { return }
self.delegate?.captureManager(self, didDetectLightingCondition: lightningCondition)
}
}
}
}
20 changes: 20 additions & 0 deletions Sources/LowLight/LightingCondition.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
//
// Copyright (c) 2019 FINN.no AS. All rights reserved.
//

enum LightingCondition: String {
case low
case normal
case high

init(value: Double) {
switch Int(round(value)) {
case Int.min..<3:
self = .low
case 14...Int.max:
self = .high
default:
self = .normal
}
}
}
61 changes: 61 additions & 0 deletions Sources/LowLight/LowLightService.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
//
// Copyright (c) 2019 FINN.no AS. All rights reserved.
//

import CoreMedia

final class LowLightService {
private let maxNumberOfResults = 3
private var results = [LightingCondition]()

func getLightningCondition(from sampleBuffer: CMSampleBuffer) -> LightingCondition? {
let rawMetadata = CMCopyDictionaryOfAttachments(
allocator: nil,
target: sampleBuffer,
attachmentMode: CMAttachmentMode(kCMAttachmentMode_ShouldPropagate)
)

do {
let metadata = CFDictionaryCreateCopy(nil, rawMetadata) as NSDictionary
let exifData: NSDictionary = try metadata.value(forKey: "{Exif}")
let fNumber: Double = try exifData.value(forKey: kCGImagePropertyExifFNumber)
let exposureTime: Double = try exifData.value(forKey: kCGImagePropertyExifExposureTime)
let isoSpeedRatings: NSArray = try exifData.value(forKey: kCGImagePropertyExifISOSpeedRatings)

guard let isoSpeedRating = isoSpeedRatings[0] as? Double else {
throw MetatataError()
}

let explosureValue = log2((100 * fNumber * fNumber) / (exposureTime * isoSpeedRating))
let lightningCondition = LightingCondition(value: explosureValue)

results.append(lightningCondition)

if results.count == maxNumberOfResults + 1 {
results = Array(results.dropFirst())
}

return results.count > 1 && Set(results).count == 1 ? lightningCondition : nil
} catch {
return nil
}
}
}

// MARK: - Private types

private extension NSDictionary {
func value<T>(forKey key: CFString) throws -> T {
return try value(forKey: key as String)
}

func value<T>(forKey key: String) throws -> T {
guard let value = self[key] as? T else {
throw MetatataError()
}

return value
}
}

private struct MetatataError: Error {}
Loading

0 comments on commit 1173ab1

Please sign in to comment.