Detect whether an UIImage is PNG or JPEG? - iphone

Currently doing my first steps with iPhone development through MonoTouch, I am playing with an UIImage that I read from the photo library.
What I want to achieve is to get the raw byte array (byte[]) of the image.
I know that there are the UIImageJPEGRepresentation and UIImagePNGRepresentation wrappers in MonoTouch. I also know how to use them. What I don't know is:
How do I decide which of these two functions to call?
I.e. if the original image is a JPEG image, I do not want to get it as an PNG but also as a JPEG, and vice versa.
Is there a way to do this, or am I missing some points on this?

Once you have a UIImage, it is capable of producing either a JPEG or PNG using UIImageJPEGRepresentation or UIImagePNGRepresentation. The format of the original image is only important when the UIImage is being created from it (decides which CFImage provider to load it).
If it's important to your app or algorithm to ensure you save it as the original format, I think you have to maintain that info to use when your writing it. I double checked and couldn't find anything that advertised what format it came from.
Are you going to change the image through UIImageView or does the image stay unchanged in your experience? If it's not changed and you just need the UI to select the image, could you get to file bytes? For example, if you showed the images just to select and then you upload them to a server or something, the UIImage could only be for viewing and selecting and if your data structure remembers which file it came from, you could get the bits back off disk and upload. If your changing the file in the view, then you or the user needs to decide the output (and if jpeg the quality) of the image.

PREPARE
typedef NS_ENUM(NSInteger, DownloadImageType) {
DownloadImageTypePng,
DownloadImageTypeJpg
};
#property (assign, nonatomic) DownloadImageType imageType;
DETECT
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info {
NSString *compareString = [[info objectForKey:UIImagePickerControllerReferenceURL] absoluteString];
NSRange pngRange = [compareString rangeOfString:#"PNG" options:NSBackwardsSearch];
if (pngRange.location != NSNotFound) {
compareString = [compareString substringFromIndex:pngRange.location];
self.imageType = DownloadImageTypePng;
NSLog(#"%#", compareString);
} else {
NSLog(#"Not PNG");
}
NSRange jpgRange = [compareString rangeOfString:#"JPG" options:NSBackwardsSearch];
if (jpgRange.location != NSNotFound) {
compareString = [compareString substringFromIndex:jpgRange.location];
self.imageType = DownloadImageTypeJpg;
NSLog(#"%#", compareString);
} else {
NSLog(#"Not JPG");
}
}
USE
if (self.imageType == DownloadImageTypePng) {
} else if (self.imageType == DownloadImageTypeJpg) {
}

Related

iOS UIImageView why Instruments show less live bytes for ImageWithContentsOfFile instead of direct image assignment from UIImagePickerController?

I've stumbled upon a peculiar live bytes issue when profiling my app with instruments. I'm interested if this is any real "optimization", or an instruments glitch. Is there any benefit to having less "live bytes" if overall real memory footprint stays the same? The real memory usage stays the same for both cases.
I'm using UIImagePicker in a popover controller, it picks an large (up to 7mb) image and assigns it to an image view like this:
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingImage:(UIImage *)img editingInfo:(NSDictionary *)editInfo {
activeImageView.image = img;
}
Here's the allocations profiling for this operation. After 2 image assignments, the live bytes has grown for each image (by about 5.4 mb). You can see the malloc block for the images below the graph (5.4mb and 768kb) Once I replace one of the images with a new one(5. mb to 768kb), the live bytes does a "step down", as displayed in the graph at about 00:55:
The peculiarity that I'm talking about is that if take this image, save it to disk and then I use imageWithContentsOfFile: as in code below, my live bytes looks very different:
//this is what I use to save image to disk
-(NSString* )saveImage:(UIImage*)image withID:(int)imageID
{
NSString* filepath = [[self imagesFolderPath] stringByAppendingPathComponent:[NSString stringWithFormat:#"%i.png",imageID]];
BOOL success = [UIImagePNGRepresentation(image) writeToFile:filepath atomically:YES];
if(!success)
{
DLog(#"failed to write to file: %#",filepath );
}
success = [[NSFileManager defaultManager]fileExistsAtPath:filepath];
DLog(#"file created: %#",success?#"YES":#"NO");
return filepath;
}
//this is the popover delegate telling me when the user has dismissed the image picker in popover
-(void)popoverControllerDidDismissPopover:(UIPopoverController *)popoverController
{
popoverController = nil;
//take the image from the image view, write it to disk and assign back to the image view
NSString* imageFilePath = [self saveImage:activeImageView.image withID:self.selectedID];
if(imageFilePath)
{
//this will work only for an existing icon file, otherwise
imageView.image =[UIImage imageWithContentsOfFile:imageFilePath];
}
}
Upon introducing this code, my live bytes allocations looks like the image below. I repeat the same operations (assigning two images to UIImageView from the image picker, but here I add a method to write them to disk, then read them back from disk. (the triangular ramp comes from when I'm creating NSData from the image for writing to disk):

Making a copy of a UIImage

I currently am using the code I found on an online tutorial to have the iPhone user select an image from their camera roll. Setting the image to the UIImageView works fine, but then I try to call a function on the image (every time a slider is moved). It turns out that the operating system is not holding onto the image, so when I try to access it (or the caching variable I made), it doesn't work (because the address gets set to 0x0000000).
If UIImage conformed to NSCopying, this would be simple. But, it doesn't. So, how do I copy an image so the operating system doesn't delete it?
Possible Duplicates
How do I make an exact copy of a UIImage returned from a UIImagePickerController?
Making deep copy of UIImage
EDIT:
The slider, when changed, calls this function:
- (IBAction)samplingSliderChanged:(id)sender {
NSLog(#"%#", self.imageStay);
float sample = self.samplingSlider.value * 0.6 + 0.2;
self.samplingRateText.text = [NSString stringWithFormat:#"%.0f%%", sample*100];
//self.imageView.image = [self.brain sampleImage:self.imageStay atRate:sample];
self.imageView.image = [self.brain sampleImage:self.imageStay.copy atRate:sample];
NSLog(#"self.imageStay: %#, self.imageStay.copy: %#", self.imageStay, self.imageStay.copy);
}
The first time the slider is moved, my self.imageStay (the cache variable) has an address of 0x0000.
My show-the-camera-roll function is below:
-(void)imagePickerController:(UIImagePickerController *)picker
didFinishPickingMediaWithInfo:(NSDictionary *)info
{
NSString *mediaType = info[UIImagePickerControllerMediaType];
UIImage * image;
[self dismissViewControllerAnimated:YES completion:nil];
image = info[UIImagePickerControllerOriginalImage];
image = [self imageWithImage:image scaledToSize:CGSizeMake(256, 256)];
self.imageView.image = image;
if (_newMedia)
UIImageWriteToSavedPhotosAlbum(image,
self,
#selector(image:finishedSavingWithError:contextInfo:),
nil);
self.imageView.image = image;
self.imageStay = image
}
In here, the cache variable has the same memory address as image, and my guess is that this later gets deleted.
You shouldn't be worrying about NSCopying, or making a copy of a UIImage, this isn't your problem. You are losing the reference to the image in your imageView. The OS is holding onto the image, if not it would disappear from your imageView. I expect you just aren't referring to it correctly. Perhaps you should show what you mean by a 'caching variable'...
In any case a reliable way to get to the image currently displayed in your imageView is to obtain it from the imageView's image property, as self.imageView.image.
update
If self.imageView.image might be subject to filtering, and you also need to keep a reference to the original image (eg in self.imageStay) you should declare the imageStay property as
#property (nonatomic, strong) UIImage* imageStay;
If you have done this correctly, and still have a nil reference in self.imageStay, step through your code in the debugger to see what is going on here:
self.imageView.image = image;
if (_newMedia)
UIImageWriteToSavedPhotosAlbum(image,
self,
#selector(image:finishedSavingWithError:contextInfo:),
nil);
self.imageView.image = image;
self.imageStay = image
self.imageStay and self.imageView.image should now both have a reference to the same object. By the way, you second assignment of self.imageView.image = image here is redundant.
Then in - (IBAction)samplingSliderChanged:(id)sender you should find those references persisting.
Your problem might be caused if you have declared your #property imageStay as a weak reference. It should be strong to ensure that it persists.

Comparing image URLs

Hi I got a set of codes here, which compares images' URL. This was from a library. An multiple imagepicker,
I know that in these codes, that the otherUrls are the images picked, while the selfUrls are the one that the photolibrary/camera roll contains.
Can someone please help me, on making this a shortcut, to not compare to every single URLs, to just skip to it, or fast comparing. Hope someone could help me. cause when it compares to all the selfUrls, it crashes. due to too much picture.
- (BOOL)isEqual:(id)other
{
if (other == self)
return YES;
if (!other || ![other isKindOfClass:[self class]])
return NO;
ALAsset *otherAsset = (ALAsset *)other;
NSDictionary *selfUrls = [self valueForProperty:ALAssetPropertyURLs];
NSDictionary *otherUrls = [otherAsset valueForProperty:ALAssetPropertyURLs];
return [selfUrls isEqualToDictionary:otherUrls];
}
From what I understand of your question this is fairly simple. I won't write the exact code for you to do it but will give you some pointers.
First off we need to find what the format of the camera roll URL looks like (so we know which ones to skip over).
So to do this create a simple call to launch the camera roll. It will have a delegate method didFinishPickingMediaWithInfo that we will use. It returns the information associated with the image you selected. We can pull the URL of the image easily.
-(void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info{
[self dismissModalViewControllerAnimated:YES];
//Get the image url
NSURL *assetURL = [info objectForKey:UIImagePickerControllerReferenceURL];
//Convert url to string
NSString *urlString = [url absoluteString];
//Print it out
NSLog(#"Our string format = %#", urlString);
}
Now that we know what the format of the camera roll URL. What you can do know is look and say, "Ok, the first 10 characters of each URL pulled from the camera roll is the same". When you loop through checking your URLs, check the first 10 characters. If they differ from the camera roll format then you know its not from the camera roll (mouthful).
Hope this helps.

App works fine on 4S, but crashes on 3G, because of SIGABRT

When the user starts the app for the first time he gets a pop up and has to save an image. It works on the simulator and on the 4S. But when I start it with my 3G it gives me a SIGABRT error as soon as I chose a picture. I assume its because of the size of the picture which is too pick and therefore claiming all of the ram - But that is rather weird, because I make it much smaller. Here is the code:
- (void)viewDidLoad
{
[super viewDidLoad];
if ([[NSUserDefaults standardUserDefaults] objectForKey:#"bild"] == nil) {
picker = [[UIImagePickerController alloc] init];
picker.delegate = self;
picker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
[self presentModalViewController:picker animated:YES];
}
}
- (void)imagePickerController:(UIImagePickerController *) Picker didFinishPickingMediaWithInfo:(NSDictionary *)info {
double compressionRatio=1;
NSData *imageData=UIImageJPEGRepresentation([info objectForKey:#"bild"],compressionRatio);
while ([imageData length]>5000) {
compressionRatio=compressionRatio*0.20;
imageData=UIImageJPEGRepresentation([info objectForKey:#"bild"],compressionRatio);
}
UIImage *yourUIImage;
yourUIImage = [info objectForKey:UIImagePickerControllerOriginalImage];
imageData = [NSKeyedArchiver archivedDataWithRootObject:yourUIImage];
[[NSUserDefaults standardUserDefaults] setObject:imageData forKey:#"bild"];
[[NSUserDefaults standardUserDefaults] synchronize];
[self dismissModalViewControllerAnimated:YES];
}
I get the SIGABRT error at this line imageData = [NSKeyedArchiver archivedDataWithRootObject:yourUIImage];
Should I use another method to resize the image? Or should I save it as a local file and retrieve it everytime the app starts? (If its possible)
You are using NSCoding to archive an UIImage into a NSData object.
Before iOS5 UIImage didn't implement the methods for NSCoding. You simply can't use archivedDataWithRootObject: with UIImages before iOS5.
That's why you get an exception on the iPhone 3G.
This is just a very educated guess because I can't confirm that with documentation or a test on a device but there are a lot of questions and forum posts around that ask how to implement NSCoding on UIImage.
and this whole block of code isn't called at all because [info objectForKey:#"bild"] will return nil. The info dictionary does not contain an object for the key bild.
Documentation for UIImagePickerControllerDelegate_Protocol contains a List of valid keys
NSData *imageData=UIImageJPEGRepresentation([info objectForKey:#"bild"],compressionRatio);
while ([imageData length]>5000) {
compressionRatio=compressionRatio*0.20;
imageData=UIImageJPEGRepresentation([info objectForKey:#"bild"],compressionRatio);
}
you probably want to use something like that:
NSData *data;
if ([[UIImage class] conformsToProtocol:#protocol(NSCoding)]) {
// >= iOS5
data = [NSKeyedArchiver archivedDataWithRootObject:image];
// save so you know it's an archived UIImage object
}
else {
// < iOS5
data = /* your UIImageJPEGRepresentation method but this time with the key UIImagePickerControllerOriginalImage instead of "bild" */
// save so you know it's raw JPEG data
}
Edit: Another error is probably that you're looking for an object for the key "bild" within the info dictionary you're passed from the image picker controller. It will have no such key. You should pass the key UIImagePickerControllerOriginalImage to get at the image instead.
--
You're not throwing away the old imageData. If your problem is high memory consumption, you'll have to do away with the image data as you progressively shrink the image.
Additionally, an image may be too big anyway to fit within 5000 bytes. Your current code doesn't handle this gracefully. Eventually, the compression ratio will grow beyond 1.0 at which point the function will probably throw an exception.
I'd suggest resizing the image before trying to compress it heavily.
No matter how you make the image data smaller, your current code keeps all the old copies around until the next time the autorelease pool is drained. Since the data returned from UIImageJPEGRepresentation is autoreleased, you may try using an inline autorelease pool around every loop iteration to drain the data immediately.
If the SIGABRT is consistently happening at imageData = [NSKeyedArchiver archivedDataWithRootObject:yourUIImage]; then my feeling would be that it is likely to be that specific line, rather than a memory issue.
UIImage never used to conform to NSCoding, but the current docs say that it does. It could be that the 3G, which will be limited to iOS 4.2.1 is using a non-conformant version and so crashes when you try to archive, whereas the 4S on iOS 5 uses the new conformant version.
Try adding a category for NSCoding to UIImage and then rerun on the 3G. Example at http://iphonedevelopment.blogspot.com/2009/03/uiimage-and-nscoding.html

Animated-GIF images received as NSData through NSSocket unable to split frames

I'm having trouble with the way iOS handles animated-GIF's. I know that you cannot use animated-GIF's on an UIImageView and you have to use custom animations on the UIImageView.
But..
I have a Java server that sends GIF images through a socketstream. The iOS (iPhone) receives that stream and converts it into an NSData type. I've succeeded in capturing and displaying this image in a UIImageView but as many of you already know.. it only displays the first frame.
I Also found some code to decode that GIF into separate images, but that code works from a GIF file and not NSData.
Question: How do i convert the NSData file into separate images and place them in an NSArray to use it as animation?
Note: In the NSData that is received are both an image and some text separated by a rare character. so the NSData looks like this: [image] [separator] [text].
Hope somebody can give me some pointers or some samples to work with..
Thanks in advance, i will keep searching untill you or me finds an answer :)
Unless you're targeting devices before IOS 4, use ImageIO.
NSMutableArray *frames = nil;
CGImageSourceRef src = CGImageSourceCreateWithData((CFDataRef)data, NULL);
if (src) {
size_t l = CGImageSourceGetCount(src);
frames = [NSMutableArray arrayWithCapacity:l];
for (size_t i = 0; i < l; i++) {
CGImageRef img = CGImageSourceCreateImageAtIndex(src, i, NULL);
if (img) {
[frames addObject:[UIImage imageWithCGImage:img]];
CGImageRelease(img);
}
}
CFRelease(src);
}
I made a wrapper that also handles animation time based on the code from Anomie
https://gist.github.com/3894888