omergul/LLSimpleCamera

Images turn out way too dark

besi opened this issue · 5 comments

besi commented

I am taking pictures with LLSimpleCamera
This is my setup:

fullsizerender

Now I did two pictures.
The first turns out to be ok:

92b58a0c-2-0

But the second one is way too dark and has a shift in color towards blue. Even though the image looks OK on the screen when taking it.

92b58a0c-2-1

Internal reference #37

I'm having the same issue

I was having the same issue, but I've found a workaround that's been working for me. In LLSimpleCamera.m, within -(void)capture:(^) exactSeenImage: , if you call to freeze the screen AFTER the captureStillImageAsynchronouslyFromConnection call, the dark images seem to stop. To be clear, the method should look like:

`
-(void)capture:(void (^)(LLSimpleCamera *camera, UIImage *image, NSDictionary *metadata, NSError *error))onCapture exactSeenImage:(BOOL)exactSeenImage
{
if(!self.session) {
NSError *error = [NSError errorWithDomain:LLSimpleCameraErrorDomain
code:LLSimpleCameraErrorCodeSession
userInfo:nil];
onCapture(self, nil, nil, error);
return;
}

// get connection and set orientation
AVCaptureConnection *videoConnection = [self captureConnection];
videoConnection.videoOrientation = [self orientationForConnection];



//videoConnection

[self.stillImageOutput captureStillImageAsynchronouslyFromConnection:videoConnection completionHandler: ^(CMSampleBufferRef imageSampleBuffer, NSError *error) {

    UIImage *image = nil;
    NSDictionary *metadata = nil;


    // check if we got the image buffer
    if (imageSampleBuffer != NULL) {
        CFDictionaryRef exifAttachments = CMGetAttachment(imageSampleBuffer, kCGImagePropertyExifDictionary, NULL);
        if(exifAttachments) {
            metadata = (__bridge NSDictionary*)exifAttachments;
        }

        NSData *imageData = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:imageSampleBuffer];
        image = [[UIImage alloc] initWithData:imageData];

        if(exactSeenImage) {
            image = [self cropImageUsingPreviewBounds:image];
        }

        if(self.fixOrientationAfterCapture) {
            image = [image fixOrientation];
        }
    }

    // trigger the block
    if(onCapture) {
        dispatch_async(dispatch_get_main_queue(), ^{
           onCapture(self, image, metadata, error);
        });
    }
}];

// freeze the screen AFTER the capture call to eliminate the occasional dark image
[self.captureVideoPreviewLayer.connection setEnabled:NO];

}`

I haven't noticed any adverse effects due to this edit. I might submit a pull request if others find this helpful.

@bfichter Did you already submit this pull request? Just realized I'm having the same issue.

besi commented

@soulfoodz, @bfichter, @griffinmacias

I could fix the problem but not by changing LLSimpleCamera but by making sure to call camera.stop() in my ViewController in the viewWillDisappear:

override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)
        
    // ...
    camera.stop()
}

Also I run camera.start() in viewWillAppear.

I think that, even when closing and re-opening the VC it would be re-used and so camera.start() would be called multiple times (without the stop call). I think this then lead to some side effects, which caused the camera view to freeze and resulting in dark or purpel-ish images.

I hope this might help you too.

besi commented

For reference here is my CameraViewController class:

import UIKit
import LLSimpleCamera
import PromiseKit

class CameraViewController: UIViewController {
    
    let isPad = UIDevice.current.userInterfaceIdiom == .pad
    
    var desiredOrientation: UIDeviceOrientation{
        if isPad{
            // Home button on the right
            return .landscapeLeft
        } else {
            // Home button on the left
            return .landscapeRight
        }
    }
    
    // MARK: - Properties
    let camera = LLSimpleCamera()

    var onCameraTouched: (()->(Void))!
    var onNextProductTouched: (()-> (Product?))!
    weak var currentProduct: Product?

    let interruptionAlert = UIAlertController(
        title: "Can't take pictures in split view mode. Please go back to fullscreen.",
        message: nil,
        preferredStyle: .alert
    )

    let portraitAlert = UIAlertController(
        title: "Please hold your device in landscape with the home button on the \(UIDevice.current.userInterfaceIdiom == .pad ? "right" : "left").",
        message: nil,
        preferredStyle: .alert
    )

    @IBOutlet weak var cropOverlay: UIView!
    
    
    // MARK: - View Lifecycle
    
    override var canBecomeFirstResponder : Bool {
        return true
    }
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)

        setupAVCaptureSession()
        startCamera()
    }
    
    override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator){
        if UIDevice.current.orientation == desiredOrientation {
            portraitAlert.dismiss(animated: true){}
        } else {
            guard !portraitAlert.isBeingPresented && !portraitAlert.isBeingDismissed else {return}
            
            // This is required probably because the popover is temporarily dismissed during rotation
            portraitAlert.dismiss(animated: false){}
            present(portraitAlert, animated: true, completion: nil)
        }
    }
    
    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        
        if interruptionAlert.isBeingPresented{
            interruptionAlert.dismiss(animated: animated, completion: nil)
        }
        
        if portraitAlert.isBeingPresented{
            portraitAlert.dismiss(animated: animated, completion: nil)
        }
        
        NotificationCenter.default.removeObserver(self)
        camera.stop()
    }
    
    override var keyCommands: [UIKeyCommand]? {
        return [
            UIKeyCommand(
                input: "\r",
                modifierFlags: [],
                action: #selector(CameraViewController.cameraTouched(_:)),
                discoverabilityTitle: "Take picture"
            ),
            
            UIKeyCommand(
                input: UIKeyInputEscape,
                modifierFlags: [],
                action: #selector(CameraViewController.closeTouched(_:)),
                discoverabilityTitle: "Exit camera"
            ),
            
            UIKeyCommand(
                input: "w",
                modifierFlags: [.command],
                action: #selector(CameraViewController.closeTouched(_:)),
                discoverabilityTitle: "Exit camera"
            ),
            
            UIKeyCommand(
                input: UIKeyInputRightArrow,
                modifierFlags: [],
                action: #selector(CameraViewController.nextProductTouched(_:)),
                discoverabilityTitle: "Next product"
            ),
        ]
    }
    
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        setupOrientation()
        setupCropOverlay()
        setupCamera()
    }
    
    
    // MARK: - Setup
    
    func setupCamera(){
        camera.attach(to: self, withFrame: calculateCameraFrame())
        camera.useDeviceOrientation = true
        camera.whiteBalanceMode = .locked
    }
    
    
    func calculateCameraFrame() -> CGRect{
        let f = isPad ? self.preferredContentSize : UIScreen.main.bounds.size
        let w = max(f.height, f.width)
        let h = min(f.height, f.width)
        return  CGRect(x: 0,y: 0, width: w, height: h)
    }
    
    
    func setupOrientation(){
        let value = desiredOrientation.rawValue
        UIDevice.current.setValue(value, forKey: "orientation")
    }
    
    func setupAVCaptureSession(){
        
        let mainQueue = OperationQueue.main
        NotificationCenter.default
            .addObserver(forName: NSNotification.Name.AVCaptureSessionWasInterrupted, object: nil, queue: mainQueue){ notification in
                guard let userInfo = (notification as NSNotification).userInfo else{ return }
                
                guard !self.interruptionAlert.isBeingPresented else {return}
                
                if let interruptionReason = userInfo[AVCaptureSessionInterruptionReasonKey] , Int(interruptionReason as! NSNumber) == AVCaptureSessionInterruptionReason.videoDeviceNotAvailableWithMultipleForegroundApps.rawValue {
                    self.present(self.interruptionAlert, animated: true, completion: nil)
                }
        }
        
        NotificationCenter.default
            .addObserver(forName: NSNotification.Name.AVCaptureSessionInterruptionEnded, object: nil, queue: mainQueue){ notification in
                self.interruptionAlert.dismiss(animated: true, completion: nil)
        }
    }
    
    func setupCropOverlay(){
        cropOverlay.layer.borderColor = UIColor.yellow.cgColor
        cropOverlay.layer.borderWidth = CGFloat(2)
        cropOverlay.backgroundColor = UIColor.clear
    }
    
    // MARK: - Actions
    
    @IBAction func closeTouched(_ sender: AnyObject) {
        presentingViewController?.dismiss(animated: true, completion: {})
    }
    
    @IBAction func nextProductTouched(_ sender: AnyObject) {
        currentProduct = onNextProductTouched()
    }
    
    @IBAction func cameraTouched(_ sender: AnyObject) {
        if let c = onCameraTouched{ c() }
    }

    func startCamera(){
        camera.start()
        view.bringSubview(toFront: cropOverlay)
    }
    
    func takePicture() -> Promise<UIImage>{
        
        if presentedViewController == portraitAlert{
            return Promise(error: NSError(domain: "camera-not-in-landscape", code: 1, userInfo: nil))
        }
        
        return Promise {fulfill, reject in
            self.camera.capture { (camera, image, options, e) -> Void in
                if let i = image{ fulfill(i) }
                if let e = e{ reject(e) }
            }
        }
    }
}