Hello everyone.

We have decided to phase out the Mixed Reality Forums over the next few months in favor of other ways to connect with us.

The first way we want to connect with you is our mixed reality developer program, which you can sign up for at https://aka.ms/IWantMR.

The plan between now and the beginning of May is to clean up old, unanswered questions that are no longer relevant. The forums will remain open and usable.

On May 1st we will be locking the forums to new posts and replies. They will remain available for another three months for the purposes of searching them, and then they will be closed altogether on August 1st.

So, where does that leave our awesome community to ask questions? Well, there are a few places we want to engage with you. For technical questions, please use Stack Overflow, and tag your questions using either hololens or windows-mixed-reality. If you want to join in discussions, please do so in the HoloDevelopers Slack, which you can join by going to https://aka.ms/holodevelopers. And always feel free to hit us up on Twitter @MxdRealityDev.

Capturing Photo in Unity and Saving to Disk

I'm getting the following exception error when trying to save a photo in Unity, based on https://developer.microsoft.com/en-us/windows/holographic/locatable_camera_in_unity. For a while I couldn't get the correct directory to save the file in, and now I think I've finally got that part working. However, now I get this exception quoted below. Any ideas on how to fix this?

I'm using the Origami example code and I've simply attached this script PhotoCaptureTest.cs to the OrigamiCollection game object in Unity.

Exception thrown: 'System.NullReferenceException' in UnityEngine.dll
NullReferenceException: Object reference not set to an instance of an object.
at UnityEngine.VR.WSA.WebCam.PhotoCapture.InvokeOnCapturedPhotoToDiskDelegate(OnCapturedToDiskCallback callback, Int64 hResult)
at UnityEngine.VR.WSA.WebCam.PhotoCapture.$Invoke9(Int64 instance, Int64* args)
at UnityEngine.Internal.$MethodUtility.InvokeMethod(Int64 instance, Int64* args, IntPtr method)
(Filename: Line: 0)

Code:

using UnityEngine;
using System.Collections;
using UnityEngine.VR.WSA.WebCam;
using System.Linq;
using Windows.Storage;
using System;
using System.IO;

public class PhotoCaptureTest : MonoBehaviour {

    PhotoCapture photoCaptureObject = null;
    string folderPath = "";
    bool haveFolderPath = false;

    // Use this for initialization
    void Start ()
    {
        getFolderPath();
        while (!haveFolderPath)
        {
            Debug.Log("Waiting for folder path...");
        }
        Debug.Log("About to call CreateAsync");
        PhotoCapture.CreateAsync(false, OnPhotoCaptureCreated);
        Debug.Log("Called CreateAsync");
    }

    // Update is called once per frame
    void Update () {

    }

    async void getFolderPath()
    {
        StorageLibrary myPictures = await Windows.Storage.StorageLibrary.GetLibraryAsync(Windows.Storage.KnownLibraryId.Pictures);
        Windows.Storage.StorageFolder savePicturesFolder = myPictures.SaveFolder;
        Debug.Log("savePicturesFolder.Path is " + savePicturesFolder.Path);
        folderPath = savePicturesFolder.Path;
        haveFolderPath = true;
    }

    void OnPhotoCaptureCreated(PhotoCapture captureObject)
    {
        photoCaptureObject = captureObject;

        Resolution cameraResolution = PhotoCapture.SupportedResolutions.OrderByDescending((res) => res.width * res.height).First();

        CameraParameters c = new CameraParameters();
        c.hologramOpacity = 0.0f;
        c.cameraResolutionWidth = cameraResolution.width;
        c.cameraResolutionHeight = cameraResolution.height;
        c.pixelFormat = CapturePixelFormat.BGRA32;

        captureObject.StartPhotoModeAsync(c, false, OnPhotoModeStarted);
    }

    void OnStoppedPhotoMode(PhotoCapture.PhotoCaptureResult result)
    {
        photoCaptureObject.Dispose();
        photoCaptureObject = null;
    }

    private void OnPhotoModeStarted(PhotoCapture.PhotoCaptureResult result)
    {
        if (result.success)
        {
            string filename = string.Format(@"\CapturedImage{0}_n.jpg", Time.time);
            string filePath = folderPath + filename;
            string currentDir = Directory.GetCurrentDirectory();
            Debug.Log("Current working direcotry is " + currentDir);
            Debug.Log("Saving photo to " + filePath);

            try
            {
                photoCaptureObject.TakePhotoAsync(filePath, PhotoCaptureFileOutputFormat.JPG, OnCapturedPhotoToDisk);
            }
            catch (System.ArgumentException e)
            {
                Debug.LogError("System.ArgumentException:\n" + e.Message);
            }
        }
        else
        {
            Debug.LogError("Unable to start photo mode!");
        }
    }

    void OnCapturedPhotoToDisk(PhotoCapture.PhotoCaptureResult result)
    {
        if (result.success)
        {
            Debug.Log("Saved Photo to disk!");
            photoCaptureObject.StopPhotoModeAsync(OnStoppedPhotoMode);
        }
        else
        {
            Debug.Log("Failed to save Photo to disk");
        }
    }
}

Best Answer

  • PatrickPatrick mod
    Accepted Answer

    @holoben

    I think this is a bug in Unity. I've found a workaround. It isn't pretty, but it works. :)
    The overview is that saving to app local storage works, and you can move files from app local storage to the pictures folder if your application has declared the pictures library capability. (which you clearly have). Turns out, even that isn't good enough. There is a 'camera roll' folder in the pictures library. That is where the photos app and the Mixed Reality Capture page on the device management website looks for photos.

    here's my updated class that works:

    #if !UNITY_EDITOR
        PhotoCapture photoCaptureObject = null;
        bool haveFolderPath = false;
        StorageFolder picturesFolder;
        string tempFilePathAndName;
        string tempFileName;
    
        // Use this for initialization
        void Start()
        {
            getFolderPath();
            while (!haveFolderPath)
            {
                Debug.Log("Waiting for folder path...");
            }
            Debug.Log("About to call CreateAsync");
            PhotoCapture.CreateAsync(false, OnPhotoCaptureCreated);
            Debug.Log("Called CreateAsync");
        }
    
        async void getFolderPath()
        {
            StorageLibrary myPictures = await Windows.Storage.StorageLibrary.GetLibraryAsync(Windows.Storage.KnownLibraryId.Pictures);
            picturesFolder = myPictures.SaveFolder;
    
            foreach(StorageFolder fodler in myPictures.Folders)
            {
                Debug.Log(fodler.Name);
    
            }
    
            Debug.Log("savePicturesFolder.Path is " + picturesFolder.Path);
            haveFolderPath = true;
        }
    
        void OnPhotoCaptureCreated(PhotoCapture captureObject)
        {
            photoCaptureObject = captureObject;
    
            Resolution cameraResolution = PhotoCapture.SupportedResolutions.OrderByDescending((res) => res.width * res.height).First();
    
            CameraParameters c = new CameraParameters();
            c.hologramOpacity = 0.0f;
            c.cameraResolutionWidth = cameraResolution.width;
            c.cameraResolutionHeight = cameraResolution.height;
            c.pixelFormat = CapturePixelFormat.BGRA32;
    
            captureObject.StartPhotoModeAsync(c, false, OnPhotoModeStarted);
        }
    
        void OnStoppedPhotoMode(PhotoCapture.PhotoCaptureResult result)
        {
            photoCaptureObject.Dispose();
            photoCaptureObject = null;
        }
    
    
    
        private void OnPhotoModeStarted(PhotoCapture.PhotoCaptureResult result)
        {
            if (result.success)
            {
                tempFileName = string.Format(@"CapturedImage{0}_n.jpg", Time.time);
    
                string filePath = System.IO.Path.Combine(Application.persistentDataPath, tempFileName);
                tempFilePathAndName = filePath;
                Debug.Log("Saving photo to " + filePath);
    
                try
                {
                    photoCaptureObject.TakePhotoAsync(filePath, PhotoCaptureFileOutputFormat.JPG, OnCapturedPhotoToDisk);
                }
                catch (System.ArgumentException e)
                {
                    Debug.LogError("System.ArgumentException:\n" + e.Message);
                }
            }
            else
            {
                Debug.LogError("Unable to start photo mode!");
            }
        }
    
        void OnCapturedPhotoToDisk(PhotoCapture.PhotoCaptureResult result)
        {
            if (result.success)
            {
                Debug.Log("Saved Photo to disk!");
                photoCaptureObject.StopPhotoModeAsync(OnStoppedPhotoMode);
                Debug.Log("moving "+tempFilePathAndName+" to " + picturesFolder.Path + "\\Camera Roll\\" + tempFileName);
                File.Move(tempFilePathAndName, picturesFolder.Path + "\\Camera Roll\\" + tempFileName);
            }
            else
            {
                Debug.Log("Failed to save Photo to disk " +result.hResult+" "+result.resultType.ToString());
            }
        }
    
    #endif
    

    ===
    This post provided as-is with no warranties and confers no rights. Using information provided is done at own risk.

    (Daddy, what does 'now formatting drive C:' mean?)

Answers

  • Short Anwer

    You probably need something like this where you have current directory (app model):

    string filePath = System.IO.Path.Combine(Application.persistentDataPath, filename);
    

    Summary

    I believe in the app model you are restricted to where you save, and maybe even by type of file, so you have to play nice and find Unity locations you can use via Application.

    My Question

    So I want to save to OneDrive and use OneDrive app for file open and save services but seems like Unity apps are restricted to certain folders under app model. Am I wrong? Also you likely have to exit Holographic Unity mode to use file picker?

  • PatrickPatrick mod
    Accepted Answer

    @holoben

    I think this is a bug in Unity. I've found a workaround. It isn't pretty, but it works. :)
    The overview is that saving to app local storage works, and you can move files from app local storage to the pictures folder if your application has declared the pictures library capability. (which you clearly have). Turns out, even that isn't good enough. There is a 'camera roll' folder in the pictures library. That is where the photos app and the Mixed Reality Capture page on the device management website looks for photos.

    here's my updated class that works:

    #if !UNITY_EDITOR
        PhotoCapture photoCaptureObject = null;
        bool haveFolderPath = false;
        StorageFolder picturesFolder;
        string tempFilePathAndName;
        string tempFileName;
    
        // Use this for initialization
        void Start()
        {
            getFolderPath();
            while (!haveFolderPath)
            {
                Debug.Log("Waiting for folder path...");
            }
            Debug.Log("About to call CreateAsync");
            PhotoCapture.CreateAsync(false, OnPhotoCaptureCreated);
            Debug.Log("Called CreateAsync");
        }
    
        async void getFolderPath()
        {
            StorageLibrary myPictures = await Windows.Storage.StorageLibrary.GetLibraryAsync(Windows.Storage.KnownLibraryId.Pictures);
            picturesFolder = myPictures.SaveFolder;
    
            foreach(StorageFolder fodler in myPictures.Folders)
            {
                Debug.Log(fodler.Name);
    
            }
    
            Debug.Log("savePicturesFolder.Path is " + picturesFolder.Path);
            haveFolderPath = true;
        }
    
        void OnPhotoCaptureCreated(PhotoCapture captureObject)
        {
            photoCaptureObject = captureObject;
    
            Resolution cameraResolution = PhotoCapture.SupportedResolutions.OrderByDescending((res) => res.width * res.height).First();
    
            CameraParameters c = new CameraParameters();
            c.hologramOpacity = 0.0f;
            c.cameraResolutionWidth = cameraResolution.width;
            c.cameraResolutionHeight = cameraResolution.height;
            c.pixelFormat = CapturePixelFormat.BGRA32;
    
            captureObject.StartPhotoModeAsync(c, false, OnPhotoModeStarted);
        }
    
        void OnStoppedPhotoMode(PhotoCapture.PhotoCaptureResult result)
        {
            photoCaptureObject.Dispose();
            photoCaptureObject = null;
        }
    
    
    
        private void OnPhotoModeStarted(PhotoCapture.PhotoCaptureResult result)
        {
            if (result.success)
            {
                tempFileName = string.Format(@"CapturedImage{0}_n.jpg", Time.time);
    
                string filePath = System.IO.Path.Combine(Application.persistentDataPath, tempFileName);
                tempFilePathAndName = filePath;
                Debug.Log("Saving photo to " + filePath);
    
                try
                {
                    photoCaptureObject.TakePhotoAsync(filePath, PhotoCaptureFileOutputFormat.JPG, OnCapturedPhotoToDisk);
                }
                catch (System.ArgumentException e)
                {
                    Debug.LogError("System.ArgumentException:\n" + e.Message);
                }
            }
            else
            {
                Debug.LogError("Unable to start photo mode!");
            }
        }
    
        void OnCapturedPhotoToDisk(PhotoCapture.PhotoCaptureResult result)
        {
            if (result.success)
            {
                Debug.Log("Saved Photo to disk!");
                photoCaptureObject.StopPhotoModeAsync(OnStoppedPhotoMode);
                Debug.Log("moving "+tempFilePathAndName+" to " + picturesFolder.Path + "\\Camera Roll\\" + tempFileName);
                File.Move(tempFilePathAndName, picturesFolder.Path + "\\Camera Roll\\" + tempFileName);
            }
            else
            {
                Debug.Log("Failed to save Photo to disk " +result.hResult+" "+result.resultType.ToString());
            }
        }
    
    #endif
    

    ===
    This post provided as-is with no warranties and confers no rights. Using information provided is done at own risk.

    (Daddy, what does 'now formatting drive C:' mean?)

  • @randyiform

    There is no file picker UI on the HoloLens yet, so you'll have to roll your own at the moment. If there was a file picker UI then, yes, it would take you out of 'exclusive mode' while the picker was active.

    ===
    This post provided as-is with no warranties and confers no rights. Using information provided is done at own risk.

    (Daddy, what does 'now formatting drive C:' mean?)

  • edited July 2016

    Hello! I'm very new to C#, Visual Studio, Unity, Hololens... all of it! I have code very similar to @holoben 's, mostly gotten from the "Locatable camera in Unity" documentation from the Windows Dev Center. I'm stuck on how to even go about testing this. I've done the same thing as holoben, where I've attached my script to the OrigamiCollection game object in Unity. Since there doesn't seem to be any apparent gesture manager for describing when to take a picture, I'm not sure how this would work when being deployed to the actual Hololens. Do I need to add something else to the OrigamiCollection to enable picture taking?

    I'm sorry if this question seems very basic--I'm really just getting into Hololens and these programs, and I'm just trying to understand how it all works. If there are any sites/ forum posts you know of that would answer this question, that would be incredibly helpful as well.

    Thanks so much!

  • @ckesterson It's actually very simple. Since PhotoCapture.CreateAsync(false, OnPhotoCaptureCreated); is called in the Start() function, it takes a photo right when the application launches. You can then find the photo either in the Photos app I believe or by using the Developer Portal via your web browser.

    To test with using gestures, look at some of the Academy tutorials that talk about gesture handling.

  • Okay perfect @holoben !! I got it working. Thanks so much!

  • I've been trying to get it to work as well but I am unable to even compile the script because it can't find the Windows.Storage API that's needed to get it to work. How did you add the reference to Windows.Storage?

  • So I couldn't figure out how to get Windows.Storage to work for me either... I don't know if this will be helpful or not, since it's just kind of a workaround, but what I ended up doing was using System.IO.Path.Combine(Application.persistentDataPath, filename) as my filepath. That doesn't store the picture in My Pictures, but it stores it in UserFiles\LocalAppData(your app)\localstate, which you can access by using the Windows Device Portal on the web. Does that make sense? Could that be helpful?

  • Do you think you could post your script that you used? I'm not very experienced with Unity programming and I'm not sure where to add that. Thanks!

  • It's actually in the second code example posted above, they've declared the String filePath in their OnPhotoStarted method. See if you can include that, and you shouldn't need to use getFolderPath().

    If you've completed the Origami tutorial (https://developer.microsoft.com/en-us/windows/holographic/holograms_101e), then you can just integrate the code from here (https://developer.microsoft.com/en-us/windows/holographic/locatable_camera_in_unity) into your GazeGestureManager script. I highly recommend completing the tutorial if you're new to unity--I'm just starting as well and it helped a ton.

  • @Patrick Hello! I was just wondering--how did you get the code you posted above to work? I was getting errors for Windows.Storage, but then I made that my namespace instead of UnityEngine.VR.WSA.WebCam, and those errors went away. But now I'm getting errors about System.IO, System.ArgumentException, and System.Convert calls, that "The type or namespace name 'IO' does not exist in the namespace 'Windows.System' (are you missing an assembly reference?)" How did you work around this? I wasn't getting these errors before when I had UnityEngine.VR.WSA.WebCam as my namespace.

    Thanks so much for your help!

  • PatrickPatrick mod
    edited August 2016

    Oh, I didn't include the using block, I was editing the OP's script... :) It should be something like:

        using UnityEngine;
        using System.Collections;
    
        using UnityEngine.VR.WSA.WebCam;
        using System.Linq;
        #if !UNITY_EDITOR
        using Windows.Storage;
        using Windows.System;
        using System.Collections.Generic;
    using System;
    using System.IO;
        #endif
    

    ===
    This post provided as-is with no warranties and confers no rights. Using information provided is done at own risk.

    (Daddy, what does 'now formatting drive C:' mean?)

  • edited August 2016

    Great, thanks so much @Patrick ! I had all of those, but I was trying to use #if NETFX_CORE around WIndows.Storage before, and that was giving me an error. For me at least, #if WINDOWS_UWP works as well.

  • WINDOWS_UWP is probably better than !UNITY_EDITOR. It's just my habit. :)

    ===
    This post provided as-is with no warranties and confers no rights. Using information provided is done at own risk.

    (Daddy, what does 'now formatting drive C:' mean?)

Sign In or Register to comment.