The only other information I could find on this error was here, which wasn't helpful.
I get the following error when I try to save images. This seems to only happen when I have several images (~6) at once. It also seems to be completely random as to when it occurs. Sometimes everything is fine, sometimes it'll fail on 1 image, sometimes 3, and sometimes the app will completely crash in a EXC_BAD_ACCESS error.
Error: ImageIO: CGImageReadGetBytesAtOffset : ^^^ ERROR ^^^ CGImageSource was created with data size: 1144891 - current size is only: 1003855
Here is the code that saves the image:
- (void)saveWithImage:(UIImage *)anImage andFileName:(NSString *)aFileName {
NSString *subDirectory = #"Images";
NSString *fileName = [aFileName stringByAppendingString:#".png"];
NSString *documentsPath = [[CMAStorageManager sharedManager] documentsSubDirectory:subDirectory].path;
NSString *imagePath = [subDirectory stringByAppendingPathComponent:fileName];
__block NSString *path = [documentsPath stringByAppendingPathComponent:fileName];
__block NSData *data = UIImagePNGRepresentation(anImage);
self.image = anImage;
self.tableCellImage = anImage;
self.galleryCellImage = anImage;
self.imagePath = imagePath; // stored path has to be relative, not absolute (iOS8 changes UUID every run)
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
if (![data writeToFile:path atomically:YES])
NSLog(#"Error saving image to path: %#", path);
dispatch_async(dispatch_get_main_queue(), ^{
});
});
}
I get that error and as a result my images aren't saved (or only half of them are saved), which completely messes up the UI display, and any subsequent app launches. I've narrowed it down to the UIImagePNGRepresentation call.
On a related note, that code locks up the UI; I think because of the UIImagePNGRepresentation call; however, as far as I know UIImagePNGRepresentation is no thread safe, so I can't do it in the background. Does anyone know a way around this?
Thanks!
In case anyone comes across a similar issue, this is what fixed it for me.
iPhone iOS saving data obtained from UIImageJPEGRepresentation() fails second time: ImageIO: CGImageRead_mapData 'open' failed
and this is the solution I used to save UIImages in a background thread:
Convert UIImage to NSData without using UIImagePngrepresentation or UIImageJpegRepresentation
I was getting this error because the api I called was throwing a 500 and returning html error page in the tmp >> NSData file I was trying to convert to PNG.
You may wish to check the file your trying to open is a image at all before conversion.
If youre downloading the file with dowloadTask then check statusCode is 200 before moving tmp > /Document/.png
heres my answer in other SO
Related
I use the iOS Simulator to test.
When select photos from its own library and store the paths to a database backed by mongodb and nodejs, I then tried to read from the path and reload the image data:
NSString *photo1Path = photo1[0];
NSLog(#"!!!photo1Path: %#", photo1Path);
// Console: !!!photo1Path: /var/folders/x4/01z7j18d7vz8rxrgcxt2w9dm0000gn/T/6-vZsBlMzA1Lj_pkdVYRx-HI.png
NSData *data = [NSData dataWithContentsOfFile:photo1Path ];
NSLog(#"!!!photo1Data: %#", data);
// Console: !!!photo1Data: (null)
UIImage *photo1Image = [UIImage imageWithData:data];
[self.photo1Button setImage:photo1Image forState:UIControlStateNormal];
As you can see from the above, the path is correct but the data is read to be "null". I tried to search solutions online but still feel confused. Is it a problem of iOS simulator or do I need to use the main bundle or something?
I am attempting to load image files as an NSString, but all of them come up nil using this code:
NSString *path = [[NSBundle mainBundle] pathForResource:[NSString stringWithUTF8String:name.data()] ofType:nil];
NSString *da = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil];
I am able to load many files, but all JPEG and PNG files fail for some reason. I thought it might have something to do with encoding so I switched it to usedEncoding, but it still didn't work.
What am I missing?
EDIT:
I have been making an iOS/Android cross platform OpenGL graphics library in C++. Everything works except texture loading. Any file loading from disk goes through one function that is abstracted between systems. I need the image file in an STL string, so that I can pass it to an image parsing library to get the raw pixel data.
I just think that it's reduculous that the function I have can open any file except images.
If you run your code, passing an NSError instance instead of nil,
NSError *error = nil;
NSString *string = [NSString stringWithContentsOfFile:filePath
encoding:NSUTF8StringEncoding
error:&error];
you will see that stringWithContentsOfFile cannot open the image file, returning nil and the error given is:
Error Domain=NSCocoaErrorDomain Code=261 "The operation couldn’t be completed. (Cocoa error 261.)"...
Cocoa error 261 is NSFileReadInapplicableStringEncodingError which means the encoding of the file is different from the one you are passing (NSUTF8StringEncoding). But I have tried with the other encodings, and none works for PNG files.
You can still achieve what you want by loading the file as a UIImage and then converting the UIImage into a Base64 string.
Since iOS 7, this is easier because you can use the built in method base64EncodedStringWithOptions:
// Load the image and convert it to NSData
UIImage *image = [UIImage imageNamed:#"yourImageName"];
NSData *imageData = UIImagePNGRepresentation(image);
// You can use the equivalent UIImageJPEGRepresentation() for JPEG images
// Convert NSData to a Base64 NSString
NSString *base64ImageString = [imageData base64EncodedStringWithOptions:NSDataBase64Encoding64CharacterLineLength];
Previous to iOS 7, you can do the exact same thing but you will have to implement your own Base64 encoding method (Or import any of the many already available, eg. nicklockwood/Base64).
I have this strange issue that I am having trouble resolving. I am creating an App which allows music to be played back. When the screen is locked (and there is a currently playing song), the lock screen will populate with a bunch of data. One piece is the album art.
The problem is that after the phone is locked and I skip a few tracks (forwards or backwards), the UIImages are no longer being loaded. If I test out the functionality and quickly skip forward and backwards in my playback queue, the album art will appear for the first 4-5 songs. After that, the images stop appearing because I get a NSFileReadNoPermissionError from my code that grabs the image. I understand that I apparently do not have permission to access the png image files, but I do not understand why. My application created them, saved them on disk, and is now trying to load them from disk while my app is running in a background state.
The relevant code snippet:
+ (void)updateLockScreenInfoAndArtForSong:(Song *)song
{
NSString *documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *artDirPath = [documentsPath stringByAppendingPathComponent:#"Album Art"];
NSString *path = artDirPath;
//-----> LIST ALL FILES for debugging <-----//
NSLog(#"LISTING ALL FILES FOUND");
int count;
NSArray *directoryContent = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:path
error:NULL];
for (count = 0; count < (int)[directoryContent count]; count++)
{
NSLog(#"File %d: %#", (count + 1), [directoryContent objectAtIndex:count]);
}
//-----> END DEBUG CODE <-----//
Song *nowPlayingSong = [MusicPlaybackController nowPlayingSong];
Class playingInfoCenter = NSClassFromString(#"MPNowPlayingInfoCenter");
if (playingInfoCenter) {
NSMutableDictionary *songInfo = [[NSMutableDictionary alloc] init];
NSError *error;
NSData *data = [NSData dataWithContentsOfURL:
[AlbumArtUtilities albumArtFileNameToNSURL:nowPlayingSong.albumArtFileName] options:NSDataReadingUncached error:&error];
NSInteger code = error.code;
NSLog(#"Error code: %li", (long)code); //prints 257 sometimes, which is NSFileReadNoPermissionError
UIImage *albumArtImage = [UIImage imageWithData:data];
if(albumArtImage == nil){ //song has no album art, check if its album does
Album *songsAlbum = song.album;
if(songsAlbum){
albumArtImage = [UIImage imageWithData:[NSData dataWithContentsOfURL:
[AlbumArtUtilities albumArtFileNameToNSURL:songsAlbum.albumArtFileName]]];
}
}
[songInfo setObject:nowPlayingSong.songName forKey:MPMediaItemPropertyTitle];
NSInteger duration = [nowPlayingSong.duration integerValue];
[songInfo setObject:[NSNumber numberWithInteger:duration]
forKey:MPMediaItemPropertyPlaybackDuration];
[[MPNowPlayingInfoCenter defaultCenter] setNowPlayingInfo:songInfo];
}
}
Any help would be immensely appreciated! I have tried so many thing that I am at a loss for what to even try next. Note the above code snippet is called in
- (void)remoteControlReceivedWithEvent:(UIEvent *)event
when event.subtype is UIEventSubtypeRemoteControlNextTrack or UIEventSubtypeRemoteControlPreviousTrack.
Figured it out after trying everything all day lol. Turns out that by default in iOS 8, much of the system is encrypted (files cannot be accessed after the phone is locked)...with a small delay of course. This is why a few album art images were loading but they stopped working after a few seconds. The delay between when the phone locked and the encryption enabled itself made it seem like my issue was "random".
Anyway for anyone reading this, my solution involved setting the file protection of all folders and subfolder and files leading to the album art directory.
Hint:
[attributes setValue:NSFileProtectionCompleteUntilFirstUserAuthentication forKey:NSFileProtectionKey];
Set that attribute on a file when it is being created (provide the information as an NSDictionary). If an existing directory or file is to be modified, gather the attributes of the thing using NSFileManager, and then set the values as shown above.
I have developed an iOS7 app. I use the UIImagePickerController to get a UIImage. During the usage of the app an image
is stored to the local app directory and loaded. The file will be overwritten several times. I use UIImagePNGRepresentation and NSData storeToFile to store
the picture. In order to load the image I use UIImage initWithContentsOfFile.
It works fine for some store and load processes. With the Debugger I can confirm that the picture is stored and loaded appropriately.
But after some time the stored picture does not contain a picture anymore. When I look at the load procedure with the debugger I can see that
a picture is loaded but it seems to be completely transparent with no other information.
This phenomenon only occurs on a device and not during a simulation with the simulator. Furthermore, the effect seems to occur only when the app is kicked out of the memory (double tap of the home button)
I debugged the app for hours but I cannot imagine the reasons for this behaviour. Additionally it is difficult to debug due to the complete termination of the app. Does anybody know a solution or has been faced with the same problem? Could anybody give me a hint what to debug to track down the problem?
Thanks in advance. Any help is appreciated. :)
Thanks for your answers. Here is a code excerpt:
Code to store image:
Generate file path in documents directory
NSString *name = [NSString stringWithFormat:#"ImageforButton%i.png",i];
NSArray *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *file = [[path objectAtIndex:0] stringByAppendingPathComponent:name];
Create NSData from UIImage (image is a UIImage that always has the correct image data)
NSData *picData = UIImagePNGRepresentation(image);
Write NSData to file
[picData writeToFile:file atomically:true];
Code to load image:
Generate file path
NSString *name = [NSString stringWithFormat:#"ImageforButton%i.png",i];
NSArray *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *file = [[path objectAtIndex:0] stringByAppendingPathComponent:name];
Load picture
UIImage *img;
if ([[NSFileManager defaultManager] fileExistsAtPath:file isDirectory:false] == true)
{
img = [UIImage imageWithContentsOfFile:[self saveFilePath:name]];
}
img does not always contain the right data. Before the app is completely terminated img
contains the expected picture. After the app was completely terminated a valid
UIImage can be loaded to img but it seems only to contain a transparent picture.
All in all it does not contain the right picture anymore.
From profiling with Instruments I have learned that the way I am saving images to disk is resulting in memory spikes to ~60MB. This results in the App emitting low memory warnings, which (inconsistently) leads crashes on the iPhone4S running iOS7.
I need the most efficient way to save an image to disk.
I am currently using this code
+ (void)saveImage:(UIImage *)image withName:(NSString *)name {
NSData *data = UIImageJPEGRepresentation(image, 1.0);
DLog(#"*** SIZE *** : Saving file of size %lu", (unsigned long)[data length]);
NSFileManager *fileManager = [NSFileManager defaultManager];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *fullPath = [documentsDirectory stringByAppendingPathComponent:name];
[fileManager createFileAtPath:fullPath contents:data attributes:nil];
}
Notes:
Reducing the value of the compressionQuality argument in UIImageJPEGRepresentation does not reduce the memory spike significantly enough.
e.g.
compressionQuality = 0.8, reduced the memory spike by 3MB on average over 100 writes.
However, it does reduce the size of the data on disk (obviously)but this does not help me.
UIImagePNGRepresentation in place of UIImageJPEGRepresentation is worse for this. It is slower and results in higher spikes.
Is it possible that this approach with ImageIO would be more efficient? If so why?
If anyone has any suggestions it would be great. Thanks
Edit:
Notes on some of the points outlined in the questions below.
a) Although I was saving multiple images, I was not saving them in a loop. I did a bit of reading around and testing and found that an autorelease pool wouldn't help me.
b) The photos were not 60Mb in size each. They were photos taken on the iPhone 4S.
With this in mind I went back to trying to overcome what I thought the problem was; the line NSData *data = UIImageJPEGRepresentation(image, 1.0);.
The memory spikes that were causing the crash can be seen in the screenshot below. They corresponded to when UIImageJPEGRepresentation was called. I also ran Time Profiler and System Usage which pointed me in the same direction.
Long story short, I moved over to AVFoundation and took the photo image data using
photoData = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:imageSampleBuffer];
Which returns an object of type NSData, I then used this as the data to write using NSFileManager.
This removes the spikes in memory completely.
i.e
[self saveImageWithData:photoData];
where
+ (void)saveImageWithData:(NSData *)imageData withName:(NSString *)name {
NSData *data = imageData;
DLog(#"*** SIZE *** : Saving file of size %lu", (unsigned long)[data length]);
NSFileManager *fileManager = [NSFileManager defaultManager];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *fullPath = [documentsDirectory stringByAppendingPathComponent:name];
[fileManager createFileAtPath:fullPath contents:data attributes:nil];
}
PS: I have not put this as an answer to the question incase people feel it does not answer the Title "Most memory efficient way to save a photo to disk on iPhone?". However, if the consensus is that it should be I can update it.
Thanks.
Using UIImageJPEGRepresentation requires that you have the original and final image in memory at the same time. It may also cache the fully rendered image for a while, which would use a lot of memory.
You could try using a CGImageDestination. I do not know how memory efficient it is, but it has the potential to stream the image directly to disk.
+(void) writeImage:(UIImage *)inImage toURL:(NSURL *)inURL withQuality:(double)inQuality {
CGImageDestinationRef destination = CGImageDestinationCreateWithURL( (CFURLRef)inURL , kUTTypeJPEG , 1 , NULL );
CFDictionaryRef properties = (CFDictionaryRef)[NSDictionary dictionaryWithObject:[NSNumber numberWithDouble:inQuality] forKey:kCGImageDestinationLossyCompressionQuality];
CGImageDestinationAddImage( destination , [inImage CGImage] , properties );
CGImageDestinationFinalize( destination );
CFRelease( destination );
}
Are your images actually 60MB compressed, each? If they are, there's not a lot you can do if you want to save them as a single JPEG file. You can try rendering them down to smaller images, or tile them and save them to separate files.
I don't expect your ImageIO code snippet to improve anything. If there were a two-line fix, then UIImageJPEGRepresentation would be using it internally.
But I'm betting that you don't get 60MB from a single image. I'm betting you get 60MB from multiple images saved in a loop. And if that's the case, then there is likely something you can do. Put an #autoreleasepool{} inside your loop. It is quite possible that you're accumulating autoreleased objects, and that's leading to the spike. Adding a pool inside your loop allows it to drain.
Try to use NSAutoReleasePool and drain the pool once u finish writing the data.