Phonegap Build iOS camera.getPicture() quality parameter not working - ios

I have written a Phonegap application and compiled it using the Build service.
My application calls the camera in this block of code:
function capturePhoto() {
// Take picture using device camera and retrieve image
navigator.camera.getPicture(onPhotoDataSuccess, onFail, {
quality: 20,
destinationType: destinationType.FILE_URI });
}
The resulting image is uploaded to a server via ajax.
When I review the uploaded images, some arrive with their quality reduced appropriately but some arrive with a filesize of ~4 MB. I have realized that users who are running my app on iPhone 4 and 5 (I have no testers with 6 yet) are consistently uploading the large images.
I have reduced quality to 10 and 5. Image size of Android-submitted images are appropriately reduced- but still IOS images are large. Can anyone tell me if there is a known issue here?
The API Docs say that you cannot reduce quality of images selected from gallery or camera roll- but these are not. They are loaded from camera.

This is the code the plugin uses for processing the image after taking the picture:
- (NSData*)processImage:(UIImage*)image info:(NSDictionary*)info options:(CDVPictureOptions*)options
{
NSData* data = nil;
switch (options.encodingType) {
case EncodingTypePNG:
data = UIImagePNGRepresentation(image);
break;
case EncodingTypeJPEG:
{
if ((options.allowsEditing == NO) && (options.targetSize.width <= 0) && (options.targetSize.height <= 0) && (options.correctOrientation == NO)){
// use image unedited as requested , don't resize
data = UIImageJPEGRepresentation(image, 1.0);
} else {
if (options.usesGeolocation) {
NSDictionary* controllerMetadata = [info objectForKey:#"UIImagePickerControllerMediaMetadata"];
if (controllerMetadata) {
self.data = data;
self.metadata = [[NSMutableDictionary alloc] init];
NSMutableDictionary* EXIFDictionary = [[controllerMetadata objectForKey:(NSString*)kCGImagePropertyExifDictionary]mutableCopy];
if (EXIFDictionary) {
[self.metadata setObject:EXIFDictionary forKey:(NSString*)kCGImagePropertyExifDictionary];
}
if (IsAtLeastiOSVersion(#"8.0")) {
[[self locationManager] performSelector:NSSelectorFromString(#"requestWhenInUseAuthorization") withObject:nil afterDelay:0];
}
[[self locationManager] startUpdatingLocation];
}
} else {
data = UIImageJPEGRepresentation(image, [options.quality floatValue] / 100.0f);
}
}
}
break;
default:
break;
};
return data;
}
So, if options.encodingType is PNG the image quality isn't change. The default value is JPG, so the problem isn't there.
But then check the
if ((options.allowsEditing == NO) && (options.targetSize.width <= 0) && (options.targetSize.height <= 0) && (options.correctOrientation == NO))
allowsEditing is NO by default
targetSize.width and targetSize.height are 0 by default
correctOrientation is NO by default
So all the conditions of the if are meet, and no change in quality is done.
It sounds like a bug as it isn't mentioned anywhere that you have to specify any other value to make the quality param work.
So, what can you do now?
You can file an issue on the cordova jira page https://issues.apache.org/jira/browse/CB
And/or you can pass any param to change the default value and make the code to skip the if and go to the else with the quality code. Changing any of this should work:
allowEdit : true,
targetWidth: 100,
targetHeight: 100,
correctOrientation: true
EDIT: there is already a bug open
https://issues.apache.org/jira/browse/CB-6190
EDIT 2: I fixed the issue, you can get the changes with cordova plugin add https://github.com/apache/cordova-plugin-camera or wait until it's released to NPM

There is no issue. iOS has larger pictures. They call it a feature.
http://lifeinlofi.com/more/iphone-photo-sizes-2007-2013/
Jesse

Related

Xamarin.Forms Xam.Plugin.Media Take Picture on iOS

I'm using the Xam.Plugin.Media in my Forms app to take pictures.
I take the Image stream (GetStream) and convert to a byte[] and store in my DB.
I then use that as the image source.
On Android and UWP, its working fine.
On iOS, if the picture is taken in portrait mode, the image once, selected is always rotated 90 deg.
I will later, upload this to a server and that image could be used on a different device.
For this, I also tried the GetStreamWithImageRotatedForExternalStorage but in this case, I cant see the image at all.
There are bytes in the stream (I DisplayAlert the length) but the image does not display.
Any idea what I might be doing wrong?
My code:-
private async Task TakePicture(WineDetails details)
{
await CrossMedia.Current.Initialize();
if (CrossMedia.Current.IsCameraAvailable && CrossMedia.Current.IsTakePhotoSupported)
{
var file = await CrossMedia.Current.TakePhotoAsync(new Plugin.Media.Abstractions.StoreCameraMediaOptions
{
AllowCropping = true,
PhotoSize = Plugin.Media.Abstractions.PhotoSize.Medium,
SaveToAlbum = false,
RotateImage = true
});
if (file == null)
return;
using (var ms = new MemoryStream())
{
var stream = file.GetStreamWithImageRotatedForExternalStorage();
stream.CopyTo(ms);
details.LabelImage = ms.ToArray();
details.NotifyChange("ImageSource");
}
}
}
The image is updated in the page via the NotifyChange and looks like this:-
ImageSource.FromStream(() => new MemoryStream(this.LabelImage))
This works fine in all cases on Android and UWP, works on iOS using GetStream (except the image is incorrectly rotated) but does not work using GetStreamWithImageRotatedForExternalStorage on iOS.
Anyone else using this plugin?
Any idea why GetStream returns a rotated image?
Any idea why GetStreamWithImageRotatedForExternalStorage is not working?
Thanks
Update:-
Changed SaveToAlbum = true and when I open the gallery, the image is rotated 90 deg.
Have RotateImage = true which could cause the issue? I'll try setting it to false.
I still can't set the image source to the byte array of the image using GetStreamWithImageRotatedForExternalStorage.
using (var ms = new MemoryStream())
{
file.GetStreamWithImageRotatedForExternalStorage().CopyTo(ms);
details.LabelImage = ms.ToArray();
}
using the byte array for an image
return ImageSource.FromStream(() => new MemoryStream(this.LabelImage));
This does not work for me, GetStream works ok.
Update:-
Ok so, RotateImage = false + GetStreamWithImageRotatedForExternalStorage allows me to display the image but its still incorrectly rotated in my app and the gallery.
I'm using this plugin, which is similar (if not the same thing - I know James Montemagno has recently packaged/bundled his work with Xamarin).
If you check the issues board there, you'll see there are quite a few people that have similar troubles (image rotation on iOS). Almost every 'solution' mentions using GetStreamWithImageRotatedForExternalStorage.
My issue was similar - I was unable to take a photo on iOS in portrait mode, without other (non-ios Devices) rotating the image. I have tried for weeks to solve this issue, but support on the plugin seems to be quite limited.
Ultimately I had to solve this with a huge workaround - using a custom renderer extending from FFImageLoading to display our images and MetadataExtractor. We were then able to extract the EXIF data from the stream and apply a rotation transformation to the FFImageLoding image control.
The rotation information was stored in a sort of weird way, as a string. This is the method I'm using to extract the rotation information, and return the amount it needs to be rotated as an int. Note that for me, iOS was able to display the image correctly still, so it's only returned a rotation change for Android devices.
public static int GetImageRotationCorrection(byte[] image)
{
try
{
var directories = ImageMetadataReader.ReadMetadata(new MemoryStream(image));
if (Device.Android == "Android")
{
foreach (var directory in directories)
{
foreach (var tag in directory.Tags)
{
if (tag.Name == "Orientation")
{
if (tag.Description == "Top, left side(Horizontal / normal)")
return 0;
else if (tag.Description == "Left side, bottom (Rotate 270 CW)")
return 270;
else if (tag.Description == "Right side, top (Rotate 90 CW")
return 90;
}
}
}
}
return 0;
}
catch (Exception ex)
{
return 0;
}
}
Note that there is also a custom renderer for the image for FFImage Loading.
public class RotatedImage : CachedImage
{
public static BindableProperty MyRotationProperty = BindableProperty.Create(nameof(MyRotation), typeof(int), typeof(RotatedImage), 0, propertyChanged: UpdateRotation);
public int MyRotation
{
get { return (int)GetValue(MyRotationProperty); }
set { SetValue(MyRotationProperty, value); }
}
private static void UpdateRotation(BindableObject bindable, object oldRotation, object newRotation)
{
var _oldRotation = (int)oldRotation;
var _newRotation = (int)newRotation;
if (!_oldRotation.Equals(_newRotation))
{
var view = (RotatedImage)bindable;
var transformations = new System.Collections.Generic.List<ITransformation>() {
new RotateTransformation(_newRotation)
};
view.Transformations = transformations;
}
}
}
So, in my XAML - I had declared a RotatedImage instead of the standard Image. With the custom renderer, I'm able to do this and have the image display rotated the correct amount.
image.MyRotation = GetImageRotationCorrection(imageAsBytes)
It's a totally unnecessary workaround - but these are the lengths that I had to go to to get around this issue.
I'll definitely be following along with this question, there might be someone in the community who could help us both!
The SaveMetaData flag is causing the rotation issue.
Setting it to false (default is true) now displays the photo correctly.
One side effect of that, the image no longer appears in the gallery if SaveToAlbum=true.
Still can't use an image take when using GetStreamWithImageRotatedForExternalStorage, even using FFImageLoading.
I found that while using Xam.Plugin.Media v5.0.1 (https://github.com/jamesmontemagno/MediaPlugin), the combination of three different inputs produced different results on Android vs. iOS:
StoreCameraMediaOptions.SaveMetaData
StoreCameraMediaOptions.RotateImage
Using MediaFile.GetStream() vs. MediaFile.GetStreamWithImageRotatedForExternalStorage()
On Android, SaveMetaData = false, RotateImage = true, and using MediaFile.GetStreamWithImageRotatedForExternalStorage() worked for me whether I was saving the result stream externally or processing the result stream locally for display.
On iOS, the combination of RotateImage = true and StreamRotated = true would result in a NullReferenceException coming out of the plugin library. Using MediaFile.GetStreamWithImageRotatedForExternalStorage() appeared to have no impact on behaivor.
--
Before going further, it's important to understand that image orientation in the JPEG format (which Xam.Plugin.Media seems to use) isn't as straightforward as you might think. Rather than rotating the raw image bytes 90 or 180 or 270 degrees, JPEG orientation can be set through embedded EXIF metadata. Orientation issues will happen with JPEGs either if EXIF data is stripped or if downstream consumers don't handle the EXIF data properly.
The approach I landed on was to normalize JPEG image orientation at the point the image is captured without relying on EXIF metadata. This way, downstream consumers shouldn't need to be relied on to properly inspect and handle EXIF orientation metadata.
The basic solution is:
Scan a JPEG for EXIF orientation metadata
Transform the JPEG to rotate/flip as needed
Set the result JPEG's orientation metadata to default
--
Code example compatible with Xamarin, using ExifLib.Standard (1.7.0) and SixLabors.ImageSharp (1.0.4) NuGet packages. Based on (Problem reading JPEG Metadata (Orientation))
using System;
using System.IO;
using ExifLib;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Metadata.Profiles.Exif;
using SixLabors.ImageSharp.Processing;
namespace Your.Namespace
{
public static class ImageOrientationUtility
{
public static Stream NormalizeOrientation(Func<Stream> inputStreamFunc)
{
using Stream exifStream = inputStreamFunc();
using var exifReader = new ExifReader(exifStream);
bool orientationTagExists = exifReader.GetTagValue(ExifTags.Orientation, out ushort orientationTagValue);
if (!orientationTagExists)
// You may wish to do something less aggressive than throw an exception in this case.
throw new InvalidOperationException("Input stream does not contain an orientation EXIF tag.");
using Stream processStream = inputStreamFunc();
using Image image = Image.Load(processStream);
switch (orientationTagValue)
{
case 1:
// No rotation required.
break;
case 2:
image.Mutate(x => x.RotateFlip(RotateMode.None, FlipMode.Horizontal));
break;
case 3:
image.Mutate(x => x.RotateFlip(RotateMode.Rotate180, FlipMode.None));
break;
case 4:
image.Mutate(x => x.RotateFlip(RotateMode.Rotate180, FlipMode.Horizontal));
break;
case 5:
image.Mutate(x => x.RotateFlip(RotateMode.Rotate90, FlipMode.Horizontal));
break;
case 6:
image.Mutate(x => x.RotateFlip(RotateMode.Rotate90, FlipMode.None));
break;
case 7:
image.Mutate(x => x.RotateFlip(RotateMode.Rotate270, FlipMode.Horizontal));
break;
case 8:
image.Mutate(x => x.RotateFlip(RotateMode.Rotate270, FlipMode.None));
break;
}
image.Metadata.ExifProfile.SetValue(ExifTag.Orientation, (ushort)1);
var outStream = new MemoryStream();
image.Save(outStream, new JpegEncoder{Quality = 100});
outStream.Position = 0;
return outStream;
}
}
}
And to use in conjunction with Xam.Plugin.Media:
MediaFile photo = await CrossMedia.Current.TakePhotoAsync(options);
await using Stream stream = ImageOrientationUtility.NormalizeOrientation(photo.GetStream);

Image rotation issue in blackberry cascades

I have an app where users can take and save their profile pic. I'm using https://github.com/RileyGB/BlackBerry10-Samples/tree/master/WebImageViewSample sample from github to load image from url to my view and it works fine. The problem is when I save the profile pic from ios and view the profile pic in blackberry it appears 90 degree rotated left. But the same url loads fine in ios and android. Below is the link of a sample image taken from iOS that loads correctly ios and android but shifts left to 90 degrees in blackberry. It works fine for other images that is taken from blackberry or android. Is there anyway to fix this? Any help is appreciated
http://oi57.tinypic.com/2hzj2c4.jpg
Below is a sample code of loading this image in qml
Page {
Container {
layout: DockLayout {
}
WebImageView {
id: webViewImage
url: "http://oi57.tinypic.com/2hzj2c4.jpg"
horizontalAlignment: HorizontalAlignment.Center
verticalAlignment: VerticalAlignment.Center
visible: (webViewImage.loading == 1.0)
}
ProgressIndicator {
value: webViewImage.loading
verticalAlignment: VerticalAlignment.Center
horizontalAlignment: HorizontalAlignment.Center
visible: (webViewImage.loading < 1.0)
}
}
actions: [
ActionItem {
title: "Clear Cache"
ActionBar.placement: ActionBarPlacement.OnBar
onTriggered: {
webViewImage.clearCache();
webViewImage.url = "http://oi57.tinypic.com/2hzj2c4.jpg";
}
}
]
}
I was able to fix this issue by adding EXIF library and adding an additional function in the webimageview class
QByteArray WebImageView::getRotateImage(QByteArray imageFile)
{
//Load the image using QImage.
// A transform will be used to rotate the image according to device and exif orientation.
QTransform transform;
QImage image;
image.loadFromData((unsigned char*)imageFile.data(),imageFile.length(),"JPG");
ExifData *exifData = 0;
ExifEntry *exifEntry = 0;
int exifOrientation = 1;
// Since the image will loose its exif data when its opened in a QImage
// it has to be manually rotated according to the exif orientation.
exifData = exif_data_new_from_data((unsigned char*)imageFile.data(),(unsigned int)imageFile.size());
// Locate the orientation exif information.
if (exifData != NULL) {
for (int i = 0; i < EXIF_IFD_COUNT; i++) {
exifEntry = exif_content_get_entry(exifData->ifd[i], EXIF_TAG_ORIENTATION);
// If the entry corresponds to the orientation it will be a non zero pointer.
if (exifEntry) {
exifOrientation = exif_get_short(exifEntry->data, exif_data_get_byte_order(exifData));
break;
}
}
}
// It's a bit tricky to get the correct orientation of the image. A combination of
// the way the the device is oriented and what the actual exif data says has to be used
// in order to rotate it in the correct way.
switch(exifOrientation) {
case 1:
// 0 degree rotation
return imageFile;
break;
case 3:
// 180 degree rotation
transform.rotate(180);
break;
case 6:
// 90 degree rotation
transform.rotate(90);
break;
case 8:
// 270 degree rotation
transform.rotate(270);
break;
default:
// Other orientations are mirrored orientations, do nothing.
break;
}
// Perform the rotation of the image before its saved.
image = image.transformed(transform);
QImage img =image;
QByteArray arr;
QBuffer buf(&arr);
buf.open(QIODevice::WriteOnly);
img.save(&buf, "PNG");
buf.close();
return arr;
}

Photo captured from Ipad will be inverted at server

I am developing an application in phonegap and have functionality of photo capturing using Ipad camera, It works fina and all photo saved properly on server. the only issue was with the fact that they are rotated 90 degree left while saved on server.
Now i tried same thing for photos stored in library and they are upload as it is on server, so i think issue is with ipad photo capturing process. Can anyone help me to found solution for such problem. Is it Ipad normal behaviour or i have mistaken in code ?
I have used below function for photo functionality
//Capture Photo either from camera or IPAD library
function capturePhoto(source){
var deferred = $q.defer();
//When source == 1 than from Photo Library
var cameraOptions = { quality: 70, destinationType: Camera.DestinationType.FILE_URI };
if(source == 1){ cameraOptions = { quality: 70, destinationType: Camera.DestinationType.FILE_URI, sourceType: navigator.camera.PictureSourceType.PHOTOLIBRARY,targetWidth: 600,targetHeight: 600 }; }
navigator.camera.getPicture( function (imageURI) {
window.resolveLocalFileSystemURI(imageURI, function (fileEntry) {
window.requestFileSystem(LocalFileSystem.PERSISTENT, 0, function(fileSys) {
fileSys.root.getDirectory("auditPhotos", {create: true, exclusive: false}, function(dir) {
fileEntry.copyTo(dir,generateRandomID()+".jpg",function (entry) {
deferred.resolve(entry.fullPath)
}, null);
}, null);
}, null);
},null);
},
function (message) {
deferred.reject(message);
},
cameraOptions
);
//deferred.resolve(generateRandomID()+".jpg");
return deferred.promise;
}
As you are saving captured image (Using above function) on server in jpg format. JPG & JPEG's have a property of EXIF which store orientation of image. When you captured image through IPAD, this exif data stores camera orientation and other position related things. But this are understood by ipad only and not by browser, that's why image was seen rotated in browser which is the truth. And same image when you viewed in IPAD it was set by IPAD in proper position due to availablity of EXIF.
To solve this problem just save your image in PNG format instead of JPG and all works fine :-)
My friend had a similar problem recently, and while I'm not too sure how to specifically solve your problem, I do know that EXIF data on the image should store something about how it's oriented.
You can try implementing this library to get the EXIF data and rotate the image if needed before the upload: https://code.google.com/p/iphone-exif/

Image loaded form redirected URL has wrong size

I am using SDWebImage to load and image from the web and set it to a marker on a map. The URL of the image may change over time. Because of that I use an url redirection which stays stable and points to the original url. Unfortunatly there is something wrong with the image when loaded via the redirection. The image is always drawn double the size than it should be when loaded from the redirection url. It is drawn correctly when loaded from the original url.
I added a logging output that puts out the image size. The output for the image loaded from the redirection url is 88x88 and for the image from the original url it is 44x44. The image has a size of 88x88 pixel. I am using a simulator with retina display, therefore I think the correct size should be 44x44 points. The redirection url definitly redirects to the correct image, I checked that in the browser. How can the size be different for the same image?
My code:
let url=NSURL(string:"http://redirectionurl/asdf")
// let url=NSURL(string:"http://originalurl.com/image#2x.png")
var managerCompletedBlock:(UIImage!, NSError!,SDImageCacheType,Bool) -> Void = {
image,error,cacheType,finished in
if(finished){
println("finished")
if let e = error
{
if let s = e.localizedDescription
{
println(s)
}
}
log.debug("size: \(image.size.width) \(image.size.height)")
imageToLoad.icon = image
} else {
println("fail!!!!");
}
}
self.imageManager.downloadWithURL(url, options: SDWebImageOptions.HighPriority, progress: nil, completed: managerCompletedBlock)
I made a screenshot. The marker on the left side is loaded from the original url and the marker on the right side is loaded from the redirection url: http://bayimg.com/JAOPmaAFp
SDWebImage has the following code in SDWebImageCompat.m, in the SDScaledImageForKey method:
if ([[UIScreen mainScreen] respondsToSelector:#selector(scale)]) {
CGFloat scale = 1.0;
if (key.length >= 8) {
// Search #2x. at the end of the string, before a 3 to 4 extension length (only if key len is 8 or more #2x. + 4 len ext)
NSRange range = [key rangeOfString:#"#2x." options:0 range:NSMakeRange(key.length - 8, 5)];
if (range.location != NSNotFound) {
scale = 2.0;
}
}
UIImage *scaledImage = [[UIImage alloc] initWithCGImage:image.CGImage scale:scale orientation:image.imageOrientation];
image = scaledImage;
}
You'll note that it's detected the standard "#2x" in the filename and is scaling the image appropriately. Looks like the key that it's checking for the #2x is, by default, just set up by the URL that's passed in, before any redirection happens.
If what you have works now, I'd probably just change your redirect URL so it ends with "#2x.png". Alternatively, from a quick glance, it looks like you can "hijack" the URL cache key by providing your own cacheKeyFilter (check the README for more info.) It's probably not intended for the purpose, but I'm guessing if you supplied a cache key filter that munged the key to always add "#2x.png" onto the end, that'd probably ensure consistent scaling behaviour.

How to get Image Orientation without loading the UIImage?

My question is quite straightforward, I want to get the orientation of an image, but I don't want to use [UIImage imageWithData:] because it consumes memory and is potentially slow. So, what would be the solution? The images are saved in the app's documents folder rather than ALAssetsLibrary.
PS: happy new year guys!
You need to use lower level Quartz functions. Read in the first 2K or 4K of the image into a NSData object, then pass that data to an incremental image creator, and ask it for the orientation. If you don't get it, read in a larger chunk. JPGs almost always have the metadata in the first 2K of data (maybe 4K, been a while since I wrote this code):
{
CGImageSourceRef imageSourcRef = CGImageSourceCreateIncremental(NULL);
CGImageSourceUpdateData(imageSourcRef, (__bridge CFDataRef)data, NO);
CFDictionaryRef dict = CGImageSourceCopyPropertiesAtIndex(imageSourcRef, 0, NULL);
if(dict) {
//CFShow(dict);
self.properties = CFBridgingRelease(dict);
if(!self.orientation) {
self.orientation = [[self.properties objectForKey:#"Orientation"] integerValue];
}
}
CFRelease(imageSourcRef);
}

Resources