Hello I would like to run a thread and check the current downloaded size of a file.
This is what I use
UIImage *image = [[UIImage alloc] initWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:#"http://lasp.colorado.edu/home/wp-content/uploads/2011/03/suncombo1.jpg"]]];
NSString *docDir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *jpegFilePath = [NSString stringWithFormat:#"%#/test.jpeg",docDir];
NSData *data2 = [NSData dataWithData:UIImageJPEGRepresentation(image, 1.0f)];//1.0f = 100% quality
[data2 writeToFile:jpegFilePath atomically:YES];
downloadStatus.text =[NSString stringWithFormat:#"size: %zd", malloc_size(data2)];
[image release];
I have also tried to change malloc_size(data2) into image but again it is not the real result. I know this does not have thread and do not check during the download process but what am I supposed to use here to see the file size?
A couple of observations:
Your question presumed that your attempts to retrieve the size of the NSData were failing. They are not. The correct way to get the size of a NSData is via length.
Your confusion, though, stems from a faulty assumption that taking an externally generated JPEG on a roundtrip through UIImage and UIImageJPEGRepresentation would yield the identical NSData. This would have been extraordinarily unlikely. There are too many different JPG settings that could have changed (see the JPEG Wikipedia page). We certainly don't know what settings that original file used. I know that UIImage and/or UIImageJPEGRepresentation changed the color space of the file. I'd wager it's doing a lot of other things, too.
So your results are correct. The original file was 2.6mb and the resulting file was 4.5mb. If you change the compressionQuality from 1.0 to 0.99, the resulting file is only 1.4mb! But if you want the original file, just save it first (like I do below).
Consider the following code which downloads the image file, saves it, loads it into a UIImage, re-extracts it via UIImageJPEGRepresentation, and saves another copy of the image:
// let's make filenames where we'll store the files
NSString *documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *suncomboOrig = [documentsPath stringByAppendingPathExtension:#"suncombo1-orig.jpg"];
NSString *suncomboReprocessed = [documentsPath stringByAppendingPathExtension:#"suncombo1-reprocessed.jpg"];
// let's download the original suncombo1.jpg and save it in Documents and display the size
NSURL *url = [NSURL URLWithString:#"http://lasp.colorado.edu/home/wp-content/uploads/2011/03/suncombo1.jpg"];
NSData *data = [NSData dataWithContentsOfURL:url];
NSLog(#"original = %d", [data length]);
[data writeToFile:suncomboOrig atomically:NO];
// let's load that into a UIImage
UIImage *image = [UIImage imageWithData:data];
// let's extract data out of the image and write that to Documents, too, also logging the size of that
NSData *data2 = UIImageJPEGRepresentation(image, 1.0);
NSLog(#"reprocessed = %d", [data2 length]);
[data2 writeToFile:suncomboReprocessed atomically:NO];
What that does is it reports:
2012-12-13 22:30:39.576 imageapp[90647:c07] original = 2569128
2012-12-13 22:30:40.141 imageapp[90647:c07] reprocessed = 4382876
So the first file I saved (which I suspect is identical to what's on your server) was 2.5mb, and the file after doing a roundtrip to a UIImage and re-extracted via 4.3mb. If I look at the two files that the above code saved, I can confirm that these NSData sizes are correct.
My original answer was predicated on the presumption that the OP was either unable to retrieve the size of a NSData or that there was some subtle issue underlying the simple question (such as wanting to get the size before the download commenced). Anyway, I've expanded my answer above, but I'll keep my original answer for historical purposes:
Original Answer:
The NSData property length tells you how many bytes were downloaded. E.g. [data2 length].
If it's really big, you can use NSURLConnection to download it asynchronously, which, depending upon your web server, may provide total file size before the download commences in the method didReceiveResponse (with the expectedContentLength property in the NSHTTPURLResponse *response parameter).
The other nice thing about NSURLConnection downloading is that you don't have to load the entire file in memory as you're downloading it, but rather you can stream it directly to persistent storage, which is especially useful if you're downloading multiple large files at the same time. If you're downloading a reasonably sized file, using NSURLConnection to download is overkill, but it can be nice when downloading large files and you want a progress indicator (or want to get the file size before the download commences).
But if you just want to know how many bytes were downloaded to your NSData, use length.
You can just use the C FILE class to get the file size.
FILE * handle = fopen([jpegFilePath UTF8String], "r");
fseek(handle, EOF); // seek to end of file
int size = ftell(handle); // returns position in bytes
fclose();
Because you say "current" size and mention a thread, I'm guessing you're trying to determine the file size as it is received. In that case, you can get the thread for free from an NSURLConnection, and you can get the data size from the delegate methods as it's received...
Create an instance variable for the downloaded data:
#property (nonatomic, strong) NSMutableData *data;
Create and launch a connection:
NSURL *url = [NSURL URLWithString:#"http://lasp.colorado.edu/home/wp-content/uploads/2011/03/suncombo1.jpg"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
self.data = [NSMutableData data];
NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
Implement the required methods in NSURLConnectionDataDelegate. For your question, the special part is this:
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
[self.data appendData:data];
NSUInteger bytesSoFar = [self.data length];
// you're on the app main thread here, so you can do something
// to the UI to indicate progress
downloadStatus.text = [NSString stringWithFormat#"size: %d", bytesSoFar];
}
A good doc on the rest of the protocol is here. When the connection is complete, you can create the image as you did with the dataWithContentsOfURL...
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
UIImage *image = [[UIImage alloc] initWithData:self.data];
downloadStatus.text = [NSString stringWithFormat#"size: %d", [self.data length]];
}
Related
I took a 13.3MB image and added it to XCode. I'm aware that when compiling, XCode performs some tricks to bring down the file size. I did this, to test how big the image now was, after being converted into data:
UIImage *image = [UIImage imageNamed:#"image.jpg"];
NSData *data = UIImageJPEGRepresentation(image, 1.0);
NSLog(#"length: %i", data.length);
The length I got back was 26758066. If that's in bytes, then it reads to me as 26.7MB. How is the image so big suddenly? Is there another way for me to get the image in data form without going through UIImage first?
EDIT: Further testing reveals that this works, and brings out a data length of ~13.3MB - the expected amount:
NSString *filePath = [[NSBundle mainBundle] pathForResource:#"image" ofType:#"jpg"];
NSData *data = [NSData dataWithContentsOfFile:filePath];
NSLog(#"length: %i", data.length);
What your code is doing is decompressing the image into memory and then recompressing as JPEG, with the highest quality ratio (q=1.0). That’s why the image is suddenly so big.
If you want to check up on the file as stored in the resource bundle, ask NSBundle for the full file path and use NSFileManager to read the file size. You can do the same thing by hand on your Mac, just take a look into the BUILD_PRODUCTS_DIR for your project.
I have code that will check to see if an image is on the phone or not (the name being retrieved from the db), and if not, it runs this code:
NSString *urlString = [NSString stringWithFormat: #"http://www.vegashipster.com/%#",image_path];
NSData *imageData = [[NSData alloc] initWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:urlString]]];
NSLog(#"saving jpg");
UIImage *image = [[UIImage alloc] initWithData:imageData];//1.0f = 100% quality
[UIImageJPEGRepresentation(image, 1.0f) writeToFile:myFilePath atomically:YES];
NSLog(#"saving image done");
NSLog(#"URL String: %#",urlString);
NSLog(#"http://www.vegashipster.com/%#",image_path);
rest_image = [UIImage imageNamed:image_path];
[image release];
[imageData release];
This code works just fine on the simulator, but on the iPhone, the screen freezes for a few seconds (downloading the image, I think), then loads the page, but with no image visible. The next time you hit that page, there is no freeze. So I believe the file is being created, but it's a messed up image file, and therefore not displayed.
I've already broken this code up so that it runs in it's own thread, and again, it works in the simulator. I had thought that if it ran behind the scenes, there would be less of a chance that the image data would get messed up, but the exact same thing happens (minus the freezing). Does anyone know what I am doing wrong with this code? Thanks for any and all help/comments.
Edit
And yes, the images being downloaded are strictly .jpg
Edit 2
I seen:
Make sure you are writing to your DOCUMENTS directory, which you have read+write access to. Otherwise you won't get any files.
at http://www.iphonedevsdk.com/forum/iphone-sdk-development/15628-file-weirdness-files-written-disk-do-not-appear-nsfilemanager-defaultmanager.html . Could this be my issue?
NSString *image_path = [[NSString stringWithUTF8String:(char *)sqlite3_column_text(statement, 5)] stringByReplacingOccurrencesOfString:#"../"
NSString *myFilePath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:image_path];
Last Edit
Well, I found what I believe to be my answer at How can I get a writable path on the iPhone? . It pretty much states I cannot save image files where my own image files are located inside the build. If this is incorrect, please let me know and I will try your way.
NSData *imageData = [[NSData alloc] initWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:urlString]]];
Is a synchronous call, ie it blocks until it has fully executed, which in the case of network operations can be 1 second, 10 seconds or 3 minutes if you don't have a time out. You are presumably running this on the main thread which is why your UI freezes (all UI stuff is done on the main thread so you must do everything not to block it). The reason it doesn't freeze the next time around is probably that it has cached the image data.
You should use asynchronous APIs, NSURLConnection has some, however I strongly recommend ASIHTTPRequest, which is an obj c wrapper around NSURLConnection and co. The code would look something like this (read through the how to use section)
- (IBAction)grabURLInBackground:(id)sender
{
NSURL *url = [NSURL URLWithString:#"http://allseeing-i.com"];
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
[request setDelegate:self];
[request startAsynchronous];
}
- (void)requestFinished:(ASIHTTPRequest *)request
{
// Use when fetching text data
NSString *responseString = [request responseString];
// Use when fetching binary data
NSData *responseData = [request responseData];
UIImage* downloadedImage = [UIImage imageWithData:responseData];
}
- (void)requestFailed:(ASIHTTPRequest *)request
{
NSError *error = [request error];
}
Sorry, forgot about this thread completely. I did end up fixing this issue. Here is what I did:
NSFileManager* fileManager = [NSFileManager defaultManager];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES);
NSString *libraryDirectory = [paths objectAtIndex:0];
NSString *myFilePath = [libraryDirectory stringByAppendingPathComponent:image_path];
BOOL fileExists = [fileManager fileExistsAtPath:myFilePath];
if (fileExists){
hotel_image = [UIImage imageWithContentsOfFile:myFilePath];
}else{
[NSThread detachNewThreadSelector:#selector(downloadImage:) toTarget:[HotelsDetailsViewController class] withObject:image_path];
fileExists = [fileManager fileExistsAtPath:myFilePath];
if (fileExists){
hotel_image = [UIImage imageWithContentsOfFile:myFilePath];
}else{
hotel_image = [UIImage imageNamed:#"hdrHotels.jpg"];
}
}
In short, I found my app's library directory and saved there.
I am attempting to save and load a UIImage to and from the iPhone documents directory after the image is picked from the iPhone Photo Library. It is able to do so, but for some reason, when I load the image, it rotates it 90 degrees counterclockwise. Here is my saveImage and loadImage methods:
Save Image:
- (void)saveImage: (UIImage*)image{
if (image != nil)
{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString* path = [documentsDirectory stringByAppendingPathComponent:
[NSString stringWithString: #"lePhoto.png"] ];
//NSData* data = UIImagePNGRepresentation(image);
//[data writeToFile:path atomically:YES];
[UIImagePNGRepresentation(image) writeToFile:path atomically:YES];
}
}
Load Image:
- (NSData*)loadImage{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString* path = [documentsDirectory stringByAppendingPathComponent:
[NSString stringWithString: #"lePhoto.png"] ];
NSData *data = [[NSData alloc] initWithContentsOfFile:path];
//UIImage *image = [[UIImage alloc] initWithData:data]; //my second try
//UIImage* image = [UIImage imageWithContentsOfFile:path]; //first try
//return image;
return data;
[data release];
}
And I am now loading the image like this, to see if it would, for some crazy reason, solve my problem (it didn't, obviously):
UIImage* theImage = [[UIImage alloc] initWithData:[self loadImage]];
I have done some testing where I have two UIImageViews side-by-side, one on the left that doesn't save and load the image before display (it just straight-up shows the image once a photo is picked from the ImagePicker), and one on the right that displays AFTER the save load occurs. The one on the right is the only one rotating 90 degrees CCW; the non Save/Load picture is displaying correctly. This only occurs on the actual iPhone. The simulator shows both images correctly. I have spent many, many hours attempting to figure it out, but to no avail. Any ideas as to what could be causing this?
EDIT: It should also be known that the image always says it's oriented up, even when it obviously isn't! It only rotates after ALL of the code has been run. I've tried doing a force redraw in between my code to see if that changes things, but it doesn't (though it is possible I'm even doing that wrong. Who knows, anymore).
Also, it will not autorotate photos that have been screenshot from the iPhone. Only photos that have been taken by the camera. [I haven't tried downloaded photos.] It's the weirdest thing...
And if you're wondering exactly how I'm pulling my picture, here's the method that will at least shed light on what I'm doing (it's not every method needed to do this function, obviously, but it gives insight on how I've written my code):
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info {
theimageView.image = [info objectForKey:#"UIImagePickerControllerOriginalImage"];
[self saveImage:[info objectForKey:#"UIImagePickerControllerOriginalImage"]];
[picker dismissModalViewControllerAnimated:YES];
}
Any advice is greatly appreciated. And I do know this much: if there's a way to do something crazy to get some stupid, never-before-heard-of problem, chances are, I'll find it, apparently :P
Ok. I figured it out. Apparently when you save things with PNG representation, and not JPEG representation, the image orientation information is not saved with it. Because of this, every image loaded will default to showing orientation up. So, the easiest way is to do this:
NSData* data = UIImageJPEGRepresentation(image,0.0);
[data writeToFile:path atomically:YES];
instead of this:
[UIImagePNGRepresentation(image) writeToFile:path atomically:YES];
And, of course, changing all the picture names from .png to .jpg. The 0.0 part of the code above is to control Alpha levels, which is required of jpegs. Hope this helps people in the future!
I assume you're using the Assets Library Framework to get the image. If so, you'll want to get the orientation of the image in your results block, and manipulate the UIImage to adjust. I have something like the following:
ALAssetsLibraryAssetForURLResultBlock resultBlock = ^(ALAsset* asset)
{
ALAssetRepresentation* rep = [asset defaultRepresentation];
_orientation = rep.orientation;
...
}
Aside: What a great type name, huh? Holy moly, does Objective-C need namespaces!
Thanks so much...
This code has worked and saved several hours of work for me.
NSData* data = UIImageJPEGRepresentation(image,0.0);
[data writeToFile:path atomically:YES];
in the place of
[UIImagePNGRepresentation(image) writeToFile:path atomically:YES];
I'm creating an app which downloads a .zip file from S3 server.
All works fine. Now I want to be able to interrupt the current download. If I could save the current size (bytes) of the file, I would be able to send a new request with a range header for the other part of the file.
Problem lies in the fact that I cannot determine the size of the 'already' downloaded content, because I can only see the file in my directory when the download is completed. So if I interrupt, there isn't a partial file saved.
At this time I use the following code for this:
-(void) downloadFile:(NSMutableArray*)paramArray withDict:(NSMutableDictionary*)options
{
NSLog(#"DOWNLOAD THREAD STARTED");
NSString * sourceUrl = [paramArray objectAtIndex:0];
NSString * fileName = [paramArray objectAtIndex:1];
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *newFilePath = [documentsDirectory stringByAppendingString:fileName];
NSError *error=[[[NSError alloc]init] autorelease];
NSURLConnection *fileURL = [NSData dataWithContentsOfURL: [NSURL URLWithString:sourceUrl]];
BOOL response = [fileURL writeToFile:newFilePath options:NSDataWritingFileProtectionNone error:&error];
if (response == TRUE)
{
NSLog(#"DOWNLOAD COMPLETED");
[self performSelectorOnMainThread:#selector(downloadComplete:withDict:) withObject:paramArray waitUntilDone:YES];
}
else
{
NSLog(#"Something went wrong while downloading file.");
NSString *callback = [NSString stringWithFormat:#"downloadInterrupted('%#');",fileName];
[webView stringByEvaluatingJavaScriptFromString:callback];
}
[pool drain];
}
AsiHTTP isn't an option because there are issues with the PhoneGap I'm using.
A better idea is to download the file asynchronously. This has several advantages: The most important one is that your user interface stays responsive. The user can go on using your application while it is downloading and waiting for the data. If the data you are downloading is absolutely essential for the application, display some sort of loading indicator.
You can easily start the asynchronous download via
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:sourceUrl]];
NSURLConnection *connection = [NSURLConnection connectionWithRequest:request delegate:self];
Now, how do I get the downloades data in an NSData object? You implement the following delegate methods for self:
-connection:didReceiveData:
-connection:didFailWithError:
-connectionDidFinishLoading:
The idea is that you are notified whenever some data drops in through your connection or anything important else happens (success or failure for exmple). So you are going to declare a temporary NSMutableData object as an instance variable (say downloadData) and write to it until the download is complete. Do not forget to initialize the empty object and declare a property as well!
-connection:didReceiveData: is called whenever some sort of data (that is, a part of your downloaded file) arrives. So you are going to append it to your temporary object like this:
-(void) connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
[self.downloadData appendData:data];
}
Once the download has finished (successfully), the next delegate method is called:
-(void) connectionDidFinishLoading:(NSURLConnection *)connection {
//do whatever you need to do with the data in self.downloadData
}
If the downloads fails, -connection:didFailWithError: is called. You can then save the temporary object, get its size and resume the download later. [self.downloadData length]; gets you the size in bytes of the data in your object.
You are going to have to use a lower level api.
time to read up on unix socket programming. http://www.fortunecity.com/skyscraper/arpanet/6/cc.htm would be a good start.
It really won't be too hard. honest.
I recommend you to build a method that save data chunk every 1, 2 MB or maybe less in order to resume properly your download and avoid memory crash.
This because if you get an error in your transfer maybe your file could be result corrupted.
Anyway send a range HTML header is pretty simple
NSFileHandle *fileHandler = [NSFileHandle fileHandleForReadingAtPath:dataPreviouslySavedPath];
[fileHandler seekToEndOfFile];
unsigned long long int range = [fileHandler offsetInFile];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:downloadURL cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:20.0];
[request setValue:[NSString stringWithFormat:#"bytes=%lli-", range] forHTTPHeaderField:#"Range"];
NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
Hope this help you.
I'm getting an image over HTTP, using NSURLConnection, as follows -
NSMutableData *receivedData;
- (void)getImage {
self.receivedData = [[NSMutableData alloc] init];
NSURLConnection *theConnection = // create connection
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
[receivedData appendData:data];
}
-(void)connectionDidFinishLoading:(NSURLConnection *)connection {
[connection release];
UIImage *theImage = [UIImage imageWithData:receivedData];
}
Usually it works just fine, but sometimes I'm seeing this get logged - : Corrupt JPEG data: premature end of data segment
At this point, the image does not completely render. I'll see maybe 75% of it, and then the lower right hand corner is a grey box.
Any ideas on how to approach fixing this? Am I constructing my image improperly?
Your HTTP code looks correct. You might want to log the size of the receivedData once it's done loading and compare it to the expected size of the image on the server. If it's the expected size, then maybe the image itself is corrupted on the server.
ASI-HTTP can fix this problem.
NSURL *coverRequestUrl = [NSURL URLWithString:imageStringURL];
ASIHTTPRequest *coverRequest = [[ASIHTTPRequest alloc] initWithURL:coverRequestUrl];
[coverRequest setDelegate:self];
[coverRequest setDidFinishSelector:#selector(imageRecieved:)];
[appDelegate.queue addOperation:coverRequest];
[appDelegate.queue go];
My queue variable in appDelegate is ASINetwork queue object. Because I send asynchronous request, so I use it.
- (void)imageRecieved:(ASIHTTPRequest *)response
{
UIImage *myImage = [UIImage imageWithData:[response responseData]];
}
I fixed this problem by using an NSMutableDictionary.
NSMutableDictionary *dataDictionary;
In my loadData function, I define my data:
NSMutableData *receivedData = receivedData = [[NSMutableData alloc] init];
Then I load the data into my dictionary where the key is [theConnection description] and the object is my data.
[dataDictionary setObject:receivedData forKey:[theConnection description]];
That way in the delegates, I can look up the correct data object for the connection that is passed to the delegate and save to the right data instance otherwise I end up with the JPEG munging/corruption problem.
In didReceiveData, I put:
//get the object for the connection that has been passed to connectionDidRecieveData and that object will be the data variable for that instance of the connection.
NSMutableData *theReceivedData = [dataDictionary objectForKey:[connection description]];
//then act on that data
[theReceivedData appendData:data];
Similarly, in didReceiveResponse, I put:
NSMutableData *theReceivedData = [dataDictionary objectForKey:[connection description]];
[theReceivedData setLength:0];
And in connectionDidFinishLoading:
NSMutableData *theReceivedData = [dataDictionary objectForKey:[connection description]];
img = [[UIImage alloc] initWithData:theReceivedData];
And this seems to work very well. By the way, my code is based on Apple's tutorial for NSUrlConnection with the addition of an NSMutableDictionary to keep track of individual connections. I hope this helps. Let me know if you want me to post my full image handling code.
I have seen this also. If you save the data to a file and then read the data back into an image, it works perfectly. I suspect there is http header information in the jpeg image data.
Hope someone finds a solution to this because the save to file workaround sucks.
// problem
UIImage *newImage = [UIImage imageWithData:receivedData];
// crappy workaround
[receivedData writeToFile:[NSString stringWithFormat:#"a.jpg"] atomically:NO];
UIImage *newImage = [UIImage imageWithContentsOfFile:#"a.jpg"];
Copying the NSData content using receivedData = [NSData dataWithBytes:receivedData.bytes length:receivedData.length] may be helpful too (and it's more efficient than saving to and reading from the disk).
A possible reason for this is that the original receivedData object does not retain its content (e.g. when created using [NSData dataWithBytesNoCopy:length:]) and you try to read them after they are freed.
This is likely when you encounter this problem on another thread from the thread that created the NSData object.