Ivar Reukers
Ivar Reukers

Reputation: 7719

Image gets rotated 90 degrees when taking portrait photo

When I'm taking an photo with my Windows Phone the landscape mode, it's perfect. The problem occurs when I'm taking a photo in portrait mode.

The photo gets rotated 90 degrees. It even occurs in the simulator as shown below.

enter image description hereenter image description here

Now this doesn't occur on Android or iOS so I assume this is because Windows is using the CameraProxy.js instead of/from cordova-plugin-camera.

My entire CameraProxy.js (Giant file, does contain 'rotate' stuff but method names are only about videos)

cordova.define("cordova-plugin-camera.CameraProxy", function(require, exports, module) {

var Camera = require('./Camera');


var getAppData = function () {
    return Windows.Storage.ApplicationData.current;
};
var encodeToBase64String = function (buffer) {
    return Windows.Security.Cryptography.CryptographicBuffer.encodeToBase64String(buffer);
};
var OptUnique = Windows.Storage.CreationCollisionOption.generateUniqueName;
var CapMSType = Windows.Media.Capture.MediaStreamType;
var webUIApp = Windows.UI.WebUI.WebUIApplication;
var fileIO = Windows.Storage.FileIO;
var pickerLocId = Windows.Storage.Pickers.PickerLocationId;

module.exports = {

    // args will contain :
    //  ...  it is an array, so be careful
    // 0 quality:50,
    // 1 destinationType:Camera.DestinationType.FILE_URI,
    // 2 sourceType:Camera.PictureSourceType.CAMERA,
    // 3 targetWidth:-1,
    // 4 targetHeight:-1,
    // 5 encodingType:Camera.EncodingType.JPEG,
    // 6 mediaType:Camera.MediaType.PICTURE,
    // 7 allowEdit:false,
    // 8 correctOrientation:false,
    // 9 saveToPhotoAlbum:false,
    // 10 popoverOptions:null
    // 11 cameraDirection:0

    takePicture: function (successCallback, errorCallback, args) {
        var sourceType = args[2];

        if (sourceType != Camera.PictureSourceType.CAMERA) {
            takePictureFromFile(successCallback, errorCallback, args);
        } else {
            takePictureFromCamera(successCallback, errorCallback, args);
        }
    }
};

// https://msdn.microsoft.com/en-us/library/windows/apps/ff462087(v=vs.105).aspx
var windowsVideoContainers = [".avi", ".flv", ".asx", ".asf", ".mov", ".mp4", ".mpg", ".rm", ".srt", ".swf", ".wmv", ".vob"];
var windowsPhoneVideoContainers =  [".avi", ".3gp", ".3g2", ".wmv", ".3gp", ".3g2", ".mp4", ".m4v"];

// Default aspect ratio 1.78 (16:9 hd video standard)
var DEFAULT_ASPECT_RATIO = '1.8';

// Highest possible z-index supported across browsers. Anything used above is converted to this value.
var HIGHEST_POSSIBLE_Z_INDEX = 2147483647;

// Resize method
function resizeImage(successCallback, errorCallback, file, targetWidth, targetHeight, encodingType) {
    var tempPhotoFileName = "";
    var targetContentType = "";

    if (encodingType == Camera.EncodingType.PNG) {
        tempPhotoFileName = "camera_cordova_temp_return.png";
        targetContentType = "image/png";
    } else {
        tempPhotoFileName = "camera_cordova_temp_return.jpg";
        targetContentType = "image/jpeg";
    }

    var storageFolder = getAppData().localFolder;
    file.copyAsync(storageFolder, file.name, Windows.Storage.NameCollisionOption.replaceExisting)
        .then(function (storageFile) {
            return fileIO.readBufferAsync(storageFile);
        })
        .then(function(buffer) {
            var strBase64 = encodeToBase64String(buffer);
            var imageData = "data:" + file.contentType + ";base64," + strBase64;
            var image = new Image();
            image.src = imageData;
            image.onload = function() {
                var ratio = Math.min(targetWidth / this.width, targetHeight / this.height);
                var imageWidth = ratio * this.width;
                var imageHeight = ratio * this.height;

                var canvas = document.createElement('canvas');
                var storageFileName;

                canvas.width = imageWidth;
                canvas.height = imageHeight;

                canvas.getContext("2d").drawImage(this, 0, 0, imageWidth, imageHeight);

                var fileContent = canvas.toDataURL(targetContentType).split(',')[1];

                var storageFolder = getAppData().localFolder;

                storageFolder.createFileAsync(tempPhotoFileName, OptUnique)
                    .then(function (storagefile) {
                        var content = Windows.Security.Cryptography.CryptographicBuffer.decodeFromBase64String(fileContent);
                        storageFileName = storagefile.name;
                        return fileIO.writeBufferAsync(storagefile, content);
                    })
                    .done(function () {
                        successCallback("ms-appdata:///local/" + storageFileName);
                    }, errorCallback);
            };
        })
        .done(null, function(err) {
            errorCallback(err);
        }
    );
}

function takePictureFromFile(successCallback, errorCallback, args) {
    // Detect Windows Phone
    if (navigator.appVersion.indexOf('Windows Phone 8.1') >= 0) {
        takePictureFromFileWP(successCallback, errorCallback, args);
    } else {
        takePictureFromFileWindows(successCallback, errorCallback, args);
    }
}

function takePictureFromFileWP(successCallback, errorCallback, args) {
    var mediaType = args[6],
        destinationType = args[1],
        targetWidth = args[3],
        targetHeight = args[4],
        encodingType = args[5];

    var filePickerActivationHandler = function(eventArgs) {
        if (eventArgs.kind === Windows.ApplicationModel.Activation.ActivationKind.pickFileContinuation) {
            var file = eventArgs.files[0];
            if (!file) {
                errorCallback("User didn't choose a file.");
                webUIApp.removeEventListener("activated", filePickerActivationHandler);
                return;
            }
            if (destinationType == Camera.DestinationType.FILE_URI || destinationType == Camera.DestinationType.NATIVE_URI) {
                if (targetHeight > 0 && targetWidth > 0) {
                    resizeImage(successCallback, errorCallback, file, targetWidth, targetHeight, encodingType);
                }
                else {
                    var storageFolder = getAppData().localFolder;
                    file.copyAsync(storageFolder, file.name, Windows.Storage.NameCollisionOption.replaceExisting).done(function (storageFile) {
                        if(destinationType == Camera.DestinationType.NATIVE_URI) {
                            successCallback("ms-appdata:///local/" + storageFile.name);
                        }
                        else {
                            successCallback(URL.createObjectURL(storageFile));
                        }
                    }, function () {
                        errorCallback("Can't access localStorage folder.");
                    });
                }
            }
            else {
                if (targetHeight > 0 && targetWidth > 0) {
                    resizeImageBase64(successCallback, errorCallback, file, targetWidth, targetHeight);
                } else {
                    fileIO.readBufferAsync(file).done(function (buffer) {
                        var strBase64 =encodeToBase64String(buffer);
                        successCallback(strBase64);
                    }, errorCallback);
                }
            }
            webUIApp.removeEventListener("activated", filePickerActivationHandler);
        }
    };

    var fileOpenPicker = new Windows.Storage.Pickers.FileOpenPicker();
    if (mediaType == Camera.MediaType.PICTURE) {
        fileOpenPicker.fileTypeFilter.replaceAll([".png", ".jpg", ".jpeg"]);
        fileOpenPicker.suggestedStartLocation = pickerLocId.picturesLibrary;
    }
    else if (mediaType == Camera.MediaType.VIDEO) {
        fileOpenPicker.fileTypeFilter.replaceAll(windowsPhoneVideoContainers);
        fileOpenPicker.suggestedStartLocation = pickerLocId.videosLibrary;
    }
    else {
        fileOpenPicker.fileTypeFilter.replaceAll(["*"]);
        fileOpenPicker.suggestedStartLocation = pickerLocId.documentsLibrary;
    }

    webUIApp.addEventListener("activated", filePickerActivationHandler);
    fileOpenPicker.pickSingleFileAndContinue();
}

function takePictureFromFileWindows(successCallback, errorCallback, args) {
    var mediaType = args[6],
        destinationType = args[1],
        targetWidth = args[3],
        targetHeight = args[4],
        encodingType = args[5];

    var fileOpenPicker = new Windows.Storage.Pickers.FileOpenPicker();
    if (mediaType == Camera.MediaType.PICTURE) {
        fileOpenPicker.fileTypeFilter.replaceAll([".png", ".jpg", ".jpeg"]);
        fileOpenPicker.suggestedStartLocation = pickerLocId.picturesLibrary;
    }
    else if (mediaType == Camera.MediaType.VIDEO) {
        fileOpenPicker.fileTypeFilter.replaceAll(windowsVideoContainers);
        fileOpenPicker.suggestedStartLocation = pickerLocId.videosLibrary;
    }
    else {
        fileOpenPicker.fileTypeFilter.replaceAll(["*"]);
        fileOpenPicker.suggestedStartLocation = pickerLocId.documentsLibrary;
    }

    fileOpenPicker.pickSingleFileAsync().done(function (file) {
        if (!file) {
            errorCallback("User didn't choose a file.");
            return;
        }
        if (destinationType == Camera.DestinationType.FILE_URI || destinationType == Camera.DestinationType.NATIVE_URI) {
            if (targetHeight > 0 && targetWidth > 0) {
                resizeImage(successCallback, errorCallback, file, targetWidth, targetHeight, encodingType);
            }
            else {
                var storageFolder = getAppData().localFolder;
                file.copyAsync(storageFolder, file.name, Windows.Storage.NameCollisionOption.replaceExisting).done(function (storageFile) {
                        if(destinationType == Camera.DestinationType.NATIVE_URI) {
                            successCallback("ms-appdata:///local/" + storageFile.name);
                        }
                        else {
                            successCallback(URL.createObjectURL(storageFile));
                        }
                }, function () {
                    errorCallback("Can't access localStorage folder.");
                });
            }
        }
        else {
            if (targetHeight > 0 && targetWidth > 0) {
                resizeImageBase64(successCallback, errorCallback, file, targetWidth, targetHeight);
            } else {
                fileIO.readBufferAsync(file).done(function (buffer) {
                    var strBase64 =encodeToBase64String(buffer);
                    successCallback(strBase64);
                }, errorCallback);
            }
        }
    }, function () {
        errorCallback("User didn't choose a file.");
    });
}

function takePictureFromCamera(successCallback, errorCallback, args) {
    // Check if necessary API available
    if (!Windows.Media.Capture.CameraCaptureUI) {
        takePictureFromCameraWP(successCallback, errorCallback, args);
    } else {
        takePictureFromCameraWindows(successCallback, errorCallback, args);
    }
}

function takePictureFromCameraWP(successCallback, errorCallback, args) {
    // We are running on WP8.1 which lacks CameraCaptureUI class
    // so we need to use MediaCapture class instead and implement custom UI for camera
    var destinationType = args[1],
        targetWidth = args[3],
        targetHeight = args[4],
        encodingType = args[5],
        saveToPhotoAlbum = args[9],
        cameraDirection = args[11],
        capturePreview = null,
        cameraCaptureButton = null,
        cameraCancelButton = null,
        capture = null,
        captureSettings = null,
        CaptureNS = Windows.Media.Capture,
        sensor = null;
    }


    function continueVideoOnFocus() {
        // if preview is defined it would be stuck, play it
        if (capturePreview) {
            capturePreview.play();
        }
    }

    function startCameraPreview() {
        // Search for available camera devices
        // This is necessary to detect which camera (front or back) we should use
        var DeviceEnum = Windows.Devices.Enumeration;
        var expectedPanel = cameraDirection === 1 ? DeviceEnum.Panel.front : DeviceEnum.Panel.back;

        // Add focus event handler to capture the event when user suspends the app and comes back while the preview is on
        window.addEventListener("focus", continueVideoOnFocus);

        DeviceEnum.DeviceInformation.findAllAsync(DeviceEnum.DeviceClass.videoCapture).then(function (devices) {
            if (devices.length <= 0) {
                destroyCameraPreview();
                errorCallback('Camera not found');
                return;
            }

            devices.forEach(function(currDev) {
                if (currDev.enclosureLocation.panel && currDev.enclosureLocation.panel == expectedPanel) {
                    captureSettings.videoDeviceId = currDev.id;
                }
            });

            captureSettings.photoCaptureSource = Windows.Media.Capture.PhotoCaptureSource.photo;

            return capture.initializeAsync(captureSettings);
        }).then(function () {

            // create focus control if available
            var VideoDeviceController = capture.videoDeviceController;
            var FocusControl = VideoDeviceController.focusControl;

            if (FocusControl.supported === true) {
                capturePreview.addEventListener('click', function () {
                    // Make sure function isn't called again before previous focus is completed
                    if (this.getAttribute('clicked') === '1') {
                        return false;
                    } else {
                        this.setAttribute('clicked', '1');
                    }
                    var preset = Windows.Media.Devices.FocusPreset.autoNormal;
                    var parent = this;
                    FocusControl.setPresetAsync(preset).done(function () {
                        // set the clicked attribute back to '0' to allow focus again
                        parent.setAttribute('clicked', '0');
                    });
                });
            }

            // msdn.microsoft.com/en-us/library/windows/apps/hh452807.aspx
            capturePreview.msZoom = true;
            capturePreview.src = URL.createObjectURL(capture);
            capturePreview.play();

            // Bind events to controls
            sensor = Windows.Devices.Sensors.SimpleOrientationSensor.getDefault();
            if (sensor !== null) {
                sensor.addEventListener("orientationchanged", onOrientationChange);
            }

            // add click events to capture and cancel buttons
            cameraCaptureButton.addEventListener('click', onCameraCaptureButtonClick);
            cameraCancelButton.addEventListener('click', onCameraCancelButtonClick);

            // Change default orientation
            if (sensor) {
                setPreviewRotation(sensor.getCurrentOrientation());
            } else {
                setPreviewRotation(Windows.Graphics.Display.DisplayInformation.getForCurrentView().currentOrientation);
            }

            // Get available aspect ratios
            var aspectRatios = getAspectRatios(capture);

            // Couldn't find a good ratio
            if (aspectRatios.length === 0) {
                destroyCameraPreview();
                errorCallback('There\'s not a good aspect ratio available');
                return;
            }

            // add elements to body
            document.body.appendChild(capturePreview);
            document.body.appendChild(cameraCaptureButton);
            document.body.appendChild(cameraCancelButton);

            if (aspectRatios.indexOf(DEFAULT_ASPECT_RATIO) > -1) {
                return setAspectRatio(capture, DEFAULT_ASPECT_RATIO);
            } else {
                // Doesn't support 16:9 - pick next best
                return setAspectRatio(capture, aspectRatios[0]);
            }
        }).done(null, function (err) {
            destroyCameraPreview();
            errorCallback('Camera intitialization error ' + err);
        });
    }

    function destroyCameraPreview() {
        // If sensor is available, remove event listener
        if (sensor !== null) {
            sensor.removeEventListener('orientationchanged', onOrientationChange);
        }

        // Pause and dispose preview element
        capturePreview.pause();
        capturePreview.src = null;

        // Remove event listeners from buttons
        cameraCaptureButton.removeEventListener('click', onCameraCaptureButtonClick);
        cameraCancelButton.removeEventListener('click', onCameraCancelButtonClick);

        // Remove the focus event handler
        window.removeEventListener("focus", continueVideoOnFocus);

        // Remove elements
        [capturePreview, cameraCaptureButton, cameraCancelButton].forEach(function (elem) {
            if (elem /* && elem in document.body.childNodes */) {
                document.body.removeChild(elem);
            }
        });

        // Stop and dispose media capture manager
        if (capture) {
            capture.stopRecordAsync();
            capture = null;
        }
    }

    function getAspectRatios(capture) {
        var videoDeviceController = capture.videoDeviceController;
        var photoAspectRatios = videoDeviceController.getAvailableMediaStreamProperties(CapMSType.photo).map(function (element) {
            return (element.width / element.height).toFixed(1);
        }).filter(function (element, index, array) { return (index === array.indexOf(element)); });

        var videoAspectRatios = videoDeviceController.getAvailableMediaStreamProperties(CapMSType.videoRecord).map(function (element) {
            return (element.width / element.height).toFixed(1);
        }).filter(function (element, index, array) { return (index === array.indexOf(element)); });

        var videoPreviewAspectRatios = videoDeviceController.getAvailableMediaStreamProperties(CapMSType.videoPreview).map(function (element) {
            return (element.width / element.height).toFixed(1);
        }).filter(function (element, index, array) { return (index === array.indexOf(element)); });

        var allAspectRatios = [].concat(photoAspectRatios, videoAspectRatios, videoPreviewAspectRatios);

        var aspectObj = allAspectRatios.reduce(function (map, item) {
            if (!map[item]) {
                map[item] = 0;
            }
            map[item]++;
            return map;
        }, {});

        return Object.keys(aspectObj).filter(function (k) {
            return aspectObj[k] === 3;
        });
    }

    function setAspectRatio(capture, aspect) {
        // Max photo resolution with desired aspect ratio
        var videoDeviceController = capture.videoDeviceController;
        var photoResolution = videoDeviceController.getAvailableMediaStreamProperties(CapMSType.photo)
            .filter(function (elem) {
                return ((elem.width / elem.height).toFixed(1) === aspect);
            })
            .reduce(function (prop1, prop2) {
                return (prop1.width * prop1.height) > (prop2.width * prop2.height) ? prop1 : prop2;
            });

        // Max video resolution with desired aspect ratio
        var videoRecordResolution = videoDeviceController.getAvailableMediaStreamProperties(CapMSType.videoRecord)
            .filter(function (elem) {
                return ((elem.width / elem.height).toFixed(1) === aspect);
            })
            .reduce(function (prop1, prop2) {
                return (prop1.width * prop1.height) > (prop2.width * prop2.height) ? prop1 : prop2;
            });

        // Max video preview resolution with desired aspect ratio
        var videoPreviewResolution = videoDeviceController.getAvailableMediaStreamProperties(CapMSType.videoPreview)
            .filter(function (elem) {
                return ((elem.width / elem.height).toFixed(1) === aspect);
            })
            .reduce(function (prop1, prop2) {
                return (prop1.width * prop1.height) > (prop2.width * prop2.height) ? prop1 : prop2;
            });

        return videoDeviceController.setMediaStreamPropertiesAsync(CapMSType.photo, photoResolution)
            .then(function () {
                return videoDeviceController.setMediaStreamPropertiesAsync(CapMSType.videoPreview, videoPreviewResolution);
            })
            .then(function () {
                return videoDeviceController.setMediaStreamPropertiesAsync(CapMSType.videoRecord, videoRecordResolution);
            });
    }


    /**
     * When the phone orientation change, get the event and change camera preview rotation
     * @param  {Object} e - SimpleOrientationSensorOrientationChangedEventArgs
     */
    function onOrientationChange(e) {
        setPreviewRotation(e.orientation);
    }

    /**
     * Converts SimpleOrientation to a VideoRotation to remove difference between camera sensor orientation
     * and video orientation
     * @param  {number} orientation - Windows.Devices.Sensors.SimpleOrientation
     * @return {number} - Windows.Media.Capture.VideoRotation
     */
    function orientationToRotation(orientation) {
        // VideoRotation enumerable and BitmapRotation enumerable have the same values
        // https://msdn.microsoft.com/en-us/library/windows/apps/windows.media.capture.videorotation.aspx
        // https://msdn.microsoft.com/en-us/library/windows/apps/windows.graphics.imaging.bitmaprotation.aspx

        switch (orientation) {
            // portrait
            case Windows.Devices.Sensors.SimpleOrientation.notRotated:
                return Windows.Media.Capture.VideoRotation.clockwise90Degrees;
            // landscape
            case Windows.Devices.Sensors.SimpleOrientation.rotated90DegreesCounterclockwise:
                return Windows.Media.Capture.VideoRotation.none;
            // portrait-flipped (not supported by WinPhone Apps)
            case Windows.Devices.Sensors.SimpleOrientation.rotated180DegreesCounterclockwise:
                // Falling back to portrait default
                return Windows.Media.Capture.VideoRotation.clockwise90Degrees;
            // landscape-flipped
            case Windows.Devices.Sensors.SimpleOrientation.rotated270DegreesCounterclockwise:
                return Windows.Media.Capture.VideoRotation.clockwise180Degrees;
            // faceup & facedown
            default:
                // Falling back to portrait default
                return Windows.Media.Capture.VideoRotation.clockwise90Degrees;
        }
    }

    /**
     * Rotates the current MediaCapture's video
     * @param {number} orientation - Windows.Devices.Sensors.SimpleOrientation
     */
    function setPreviewRotation(orientation) {
        capture.setPreviewRotation(orientationToRotation(orientation));
    }

    try {
        createCameraUI();
        startCameraPreview();
    } catch (ex) {
        errorCallback(ex);
    }
}

function takePictureFromCameraWindows(successCallback, errorCallback, args) {
    var destinationType = args[1],
        targetWidth = args[3],
        targetHeight = args[4],
        encodingType = args[5],
        allowCrop = !!args[7],
        saveToPhotoAlbum = args[9],
        WMCapture = Windows.Media.Capture,
        cameraCaptureUI = new WMCapture.CameraCaptureUI();

    cameraCaptureUI.photoSettings.allowCropping = allowCrop;

    if (encodingType == Camera.EncodingType.PNG) {
        cameraCaptureUI.photoSettings.format = WMCapture.CameraCaptureUIPhotoFormat.png;
    } else {
        cameraCaptureUI.photoSettings.format = WMCapture.CameraCaptureUIPhotoFormat.jpeg;
    }

    // decide which max pixels should be supported by targetWidth or targetHeight.
    var maxRes = null;
    var UIMaxRes = WMCapture.CameraCaptureUIMaxPhotoResolution;
    var totalPixels = targetWidth * targetHeight;

    if (targetWidth == -1 && targetHeight == -1) {
        maxRes = UIMaxRes.highestAvailable;
    }
    // Temp fix for CB-10539
    /*else if (totalPixels <= 320 * 240) {
        maxRes = UIMaxRes.verySmallQvga;
    }*/
    else if (totalPixels <= 640 * 480) {
        maxRes = UIMaxRes.smallVga;
    } else if (totalPixels <= 1024 * 768) {
        maxRes = UIMaxRes.mediumXga;
    } else if (totalPixels <= 3 * 1000 * 1000) {
        maxRes = UIMaxRes.large3M;
    } else if (totalPixels <= 5 * 1000 * 1000) {
        maxRes = UIMaxRes.veryLarge5M;
    } else {
        maxRes = UIMaxRes.highestAvailable;
    }

    cameraCaptureUI.photoSettings.maxResolution = maxRes;

    var cameraPicture;

    // define focus handler for windows phone 10.0
    var savePhotoOnFocus = function () {
        window.removeEventListener("focus", savePhotoOnFocus);
        // call only when the app is in focus again
        savePhoto(cameraPicture, {
            destinationType: destinationType,
            targetHeight: targetHeight,
            targetWidth: targetWidth,
            encodingType: encodingType,
            saveToPhotoAlbum: saveToPhotoAlbum
        }, successCallback, errorCallback);
    };

    cameraCaptureUI.captureFileAsync(WMCapture.CameraCaptureUIMode.photo).done(function (picture) {
        if (!picture) {
            errorCallback("User didn't capture a photo.");
            // Remove the focus handler if present
            window.removeEventListener("focus", savePhotoOnFocus);
            return;
        }
        cameraPicture = picture;

        // If not windows 10, call savePhoto() now. If windows 10, wait for the app to be in focus again
        if (navigator.appVersion.indexOf('Windows Phone 10.0') < 0) {
            savePhoto(cameraPicture, {
                destinationType: destinationType,
                targetHeight: targetHeight,
                targetWidth: targetWidth,
                encodingType: encodingType,
                saveToPhotoAlbum: saveToPhotoAlbum
            }, successCallback, errorCallback);
        }
    }, function () {
        errorCallback("Fail to capture a photo.");
        window.removeEventListener("focus", savePhotoOnFocus);
    });
}

require("cordova/exec/proxy").add("Camera",module.exports);

});

Does anyone know how I can keep my image rotation in Windows?

Upvotes: 3

Views: 917

Answers (1)

Tummala Krishna Kishore
Tummala Krishna Kishore

Reputation: 8271

In your CameraProxy.js make changes in orientationToRotation function line number 569.

case Windows.Devices.Sensors.SimpleOrientation.notRotated:
            if (cameraDirection == 0) {
                return Windows.Media.Capture.VideoRotation.clockwise90Degrees;
            }
            else {
                return Windows.Media.Capture.VideoRotation.clockwise270Degrees;
            }

For More Info you can refer this Solution

Upvotes: 2

Related Questions