Compress Large UIImage - iphone

so what im doing is compressing an image repeatedly until it fits within a size frame. It it takes to many attempts, give up and later we tell the user the image is to large.
My issue is that after the image size has been reduced, its size goes WAY back up again when its changed to a UIImage.
Here is the code for reducing the image size:
double compressionRatio = 1;
int resizeAttempts = 5;
NSData * imgData = UIImageJPEGRepresentation([info objectForKey:#"UIImagePickerControllerOriginalImage"],compressionRatio);
NSLog(#"Starting Size: %i", [imgData length]);
//Trying to push it below around about 0.4 meg
while ([imgData length] > 400000 && resizeAttempts > 0) {
resizeAttempts -= 1;
NSLog(#"Image was bigger than 400000 Bytes. Resizing.");
NSLog(#"%i Attempts Remaining",resizeAttempts);
//Increase the compression amount
compressionRatio = compressionRatio*0.5;
//Test size before compression
NSLog(#"Current Size: %i",[imgData length]);
imgData = UIImageJPEGRepresentation([info objectForKey:#"UIImagePickerControllerOriginalImage"],compressionRatio);
//Test size after compression
NSLog(#"New Size: %i",[imgData length]);
}
//Set image by comprssed version
savedImage = [UIImage imageWithData:imgData];
//Check how big the image is now its been compressed and put into the UIImageView
NSData *endData = UIImageJPEGRepresentation(savedImage,1.0);
NSLog(#"Ending Size: %i", [endData length]);
Ok now here is the console report:
Starting Image Size: 4076994
Image was bigger than 400000 Bytes. Resizing.
4 Attempts Remaining
Current Size: 4076994
New Compressed Size: 844482
Image was bigger than 400000 Bytes. Resizing.
3 Attempts Remaining
Current Size: 844482
New Compressed Size: 357459
Ending Image Size: 2090332
As you can see the image started at 4mb big. It was compressed to 350kb, then in the end it was made into a UIImage as 2mb.
Thanks in advance to anyone who can make any sense of this.

I have modified your function a bit and got result, try it, it reduce my 10MB image to 3MB.
-(void)compraseImage
{
UIImage *largeImage = [info objectForKey:#"UIImagePickerControllerOriginalImage"];
double compressionRatio = 1;
int resizeAttempts = 5;
NSData * imgData = UIImageJPEGRepresentation(largeImage,compressionRatio);
NSLog(#"Starting Size: %i", [imgData length]);
//Trying to push it below around about 0.4 meg
while ([imgData length] > 400000 && resizeAttempts > 0) {
resizeAttempts -= 1;
NSLog(#"Image was bigger than 400000 Bytes. Resizing.");
NSLog(#"%i Attempts Remaining",resizeAttempts);
//Increase the compression amount
compressionRatio = compressionRatio*0.5;
NSLog(#"compressionRatio %f",compressionRatio);
//Test size before compression
NSLog(#"Current Size: %i",[imgData length]);
imgData = UIImageJPEGRepresentation(largeImage,compressionRatio);
//Test size after compression
NSLog(#"New Size: %i",[imgData length]);
}
//Set image by comprssed version
savedImage = [UIImage imageWithData:imgData];
//Check how big the image is now its been compressed and put into the UIImageView
// *** I made Change here, you were again storing it with Highest Resolution ***
NSData *endData = UIImageJPEGRepresentation(largeImage,compressionRatio);
NSLog(#"Ending Size: %i", [endData length]);
NSString *path = [self createPath:#"myImage.jpg"];
NSLog(#"%#",path);
[endData writeToFile:path atomically:YES];
}
-(NSString *) createPath:(NSString *)withFileName
{
NSArray *paths =NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask,
YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *path = [documentsDirectory stringByAppendingPathComponent:withFileName];
return path;
}

Related

ALAssets get video data

I am trying to access video data from ALAssets library using the below code
ALAssetRepresentation *rep = [asset defaultRepresentation];
Byte *buffer = (Byte*)malloc(rep.size);
NSError *error = nil;
NSUInteger buffered = [rep getBytes:buffer fromOffset:0.0 length:rep.size error:&error];
NSData *data = [NSData dataWithBytesNoCopy:buffer length:buffered freeWhenDone:YES];
It works fine for small video as well as pictures, But if am trying to get a large video, the code crashes saying
* Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '* -[NSConcreteData initWithBytes:length:copy:freeWhenDone:bytesAreVM:]: absurd length: 4294967295, maximum size: 2147483648 bytes'
I don't have an idea what's going on. Any one any thoughts?
Thanks in advance!
I found the solution. I guess the crash may be due to huge memory spike when we upload large files, because I am buffering data. Now I read file data as 5 MB chunks and this fix the crash. I am pasting my code below.
- (NSData *)getDataPartAtOffset:(NSInteger)offset {
__block NSData *chunkData = nil;
if (fileAsset_){
static const NSUInteger BufferSize = PART_SIZE; // 5 MB chunk
ALAssetRepresentation *rep = [fileAsset_ defaultRepresentation];
uint8_t *buffer = calloc(BufferSize, sizeof(*buffer));
NSUInteger bytesRead = 0;
NSError *error = nil;
#try
{
bytesRead = [rep getBytes:buffer fromOffset:offset length:BufferSize error:&error];
chunkData = [NSData dataWithData:[NSData dataWithBytesNoCopy:buffer length:bytesRead freeWhenDone:NO]];
}
#catch (NSException *exception)
{
free(buffer);
chunkData = nil;
// Handle the exception here...
}
free(buffer);
} else {
NSLog(#"failed to retrive Asset");
}
return chunkData;
}
And I I'll call this function as
int offset = 0; // offset that keep tracks of chunk data
do {
#autoreleasepool {
NSData *chunkData = [self getDataPartAtOffset:offset];;
if (!chunkData || ![chunkData length]) { // finished reading data
break;
}
// do your stuff here
offset +=[chunkData length];
}
} while (1);
chilitechno's bit here worked for me.

large number of large files creating leak while creating zip file

NSData * buffer = [fileHandle readDataOfLength:chunkSize];
while ([buffer length] > 0)
{
[streamBIG writeData:buffer];
offset += [buffer length];
[fileHandle seekToFileOffset:offset];
buffer = [fileHandle readDataOfLength:chunkSize];
}
I use these particular process to zip more then 1 file of 25 - 30 MB but these increases memory i.e. live bytes continuously increase till all files are not written, because of which my app crashes
Try like this with Auto release pool
NSData * buffer = [fileHandle readDataOfLength:chunkSize];
while ([buffer length] > 0)
{
#autoreleasepool
{
[streamBIG writeData:buffer];
offset += [buffer length];
[fileHandle seekToFileOffset:offset];
buffer = [fileHandle readDataOfLength:chunkSize];
}
}
it will work...

how can i handling UIImage using a pointer?

i'm studying iOS programming.
i have to handling an image.
i must make an image to BMP file. but iOS doesn't support to make BMP file.
so i think that maybe i use a pointer i can make BMP file.
i make a header for BMP. and put it in an NSData object.
now i left saving image data's to that NSData.(i'll say bmpData)
here's start my wondering.
i have to have image which size is 128 x 64.
i get the UIImage from a context. (i'll say image1)
and i make a CGSize which is 128 x 64.
i make a context2 using that size, drawing an image1
now i get the UIImage from a context2, which size is 128 x 64, resized image1.
and i make an UIImageView and use image2, it works fine. good. image2 is made well.
so i declared a pointer, which is unsigned char *.
unsigned char *bmpDataPointer = (unsigned char *)image2;
and i use for loop
for(int i = 0; i < 64; ++i)
{
for(int j = 0 ; j < 128; ++j)
{
// dataObj and bmpData are different NSData object
// dataObj just contain bitmap data to check my pointer works fine or not.
[dataObj appendBytes:&(bitmapDataPointer[i*1 + j]) length:sizeof(char)];
}
}
and i make a UIImage to check that data is valid, it fails.
UIImage *createdImageUsedByaPointer = [UIImage imageWithData:dataObj];
if(createdImageUsedByaPointer == nil)
{
NSLog(#"nil!");
}
ok run. then string nil will be presented.
why is that? i make context size 128 x 64, so i loop 128 x 64 times.
but it works bad.
how can i fix that??
how can i handling an UIImage to use a pointer??
anybody knows about that please help me.
Don't know why you need .bmp format but ImageMagick will handle export.
Use this lib ImageMagick for iOS and the code below.
UIImage *img9 = [UIImage imageNamed:#"00009_face.jpg"];
MagickWand *bmpWand = NewMagickWand();
NSData *bmpObj = UIImagePNGRepresentation(img9);
MagickSetFormat(bmpWand, "bmp");
MagickReadImageBlob(bmpWand, [bmpObj bytes], [bmpObj length]);
size_t bmp_size;
unsigned char * bmp_image = MagickGetImagesBlob(bmpWand, &bmp_size);
NSData *bmpData = [[[NSData alloc] initWithBytes:bmp_image length:bmp_size] autorelease];
free(bmp_image);
DestroyMagickWand(bmpWand);
NSArray *pathsArray = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString * documentDirectory = [pathsArray objectAtIndex:0];
NSString * bmpPath = [documentDirectory stringByAppendingPathComponent:#"test2.bmp"];
[bmpData writeToFile:bmpPath atomically:NO];
NSLog(#"bmpPath %#", bmpPath);
Why are you going in that much complexity?
You can create bitmap context as follows:
CGContextRef context = CGBitmapContextCreate(data, width, height,
bitsPerComponent,
bytesPerRow, colorSpace,
kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
CGColorSpaceRelease(colorSpace);
CGImageRef imageRef = CGBitmapContextCreateImage(context);
UIImage *result = [UIImage imageWithCGImage:imageRef];
here data is ur image's NSData

Got wrong value while detecting available disk space on iphone

I follow http://kdbdallas.com/2008/12/27/maciphone-show-availble-useable-diskspace/
and how do i find out iphone Disk Space?
to detect the available disk space. However, seem that the result is not correct
I tested on iphone 4 ios 4.2.1.
in Setting,
Capacity is 29.1 G
Availability is 24.2 G
I want to get 24.2 G value. So I followed the tutorial I mentioned above.
int main(int argc, char *argv[]) {
.....
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSLog(#"path %#", paths);
struct statfs tStats;
statfs([[paths lastObject] UTF8String], &tStats);
NSString *sizeType;
float total_space = (float)(tStats.f_blocks * tStats.f_bsize);
NSLog(#"total space: %f", total_space);
NSLog(#"total space: %#", convertUnit(total_space));
float free_space = (float)(tStats.f_bfree * tStats.f_bsize);
NSLog(#"free space: %f", free_space);
NSLog(#"free space: %#", convertUnit(free_space));
float free_space2 = (float)(tStats.f_bavail * tStats.f_bsize);
NSLog(#"free blocks avail to non-superuser: %f", free_space2);
NSLog(#"free blocks avail to non-superuser: %#", convertUnit(free_space2));
uint32_t block_size = (tStats.f_bsize);
NSLog(#"block size: %d", block_size);
NSLog(#"block size: %#", convertUnit(block_size));
.....
}
NSString* convertUnit (float value) {
NSString *sizeType;
if (value > 1024)
{
//Kilobytes
value = value / 1024;
sizeType = #" KB";
}
if (value > 1024)
{
//Megabytes
value = value / 1024;
sizeType = #" MB";
}
if (value > 1024)
{
//Gigabytes
value = value / 1024;
sizeType = #" GB";
}
return [[#"Available Disk Space: " stringByAppendingFormat:#"%.2f", value] stringByAppendingString:sizeType];
}
RESULT:
total space: 17363854862722269184.000000
total space: Available Disk Space: 16171350016.00 GB
free space: 17743592094095638528.000000
free space: Available Disk Space: 16525007872.00 GB
free blocks avail to non-superuser: 17432956969504735232.00000
free blocks avail to non-superuser: Available Disk Space: 16235706368.00 GB
block size: 803203484
block size: Available Disk Space: 765.99 MB
After do more search about file system of iphone, I found that iphone has 2 partitions: one for os and another for user data. The values I got is the attributes of OS partitions. So I changed the path to be 'private/var' which is the path of data partition. As a result, I got the correct value for free disk space.
Try this code
-(uint64_t)getFreeDiskspace {
uint64_t totalSpace = 0;
uint64_t totalFreeSpace = 0;
NSError *error = nil;
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask, YES);
NSDictionary *dictionary = [[NSFileManager defaultManager] attributesOfFileSystemForPath:[paths lastObject] error: &error];
if (dictionary) {
NSNumber *fileSystemSizeInBytes = [dictionary objectForKey: NSFileSystemSize];
NSNumber *freeFileSystemSizeInBytes = [dictionary objectForKey:NSFileSystemFreeSize];
totalSpace = [fileSystemSizeInBytes unsignedLongLongValue];
totalFreeSpace = [freeFileSystemSizeInBytes unsignedLongLongValue];
NSLog(#"Memory Capacity of %llu MiB with %llu MiB Free memory available.", ((totalSpace/1024ll)/1024ll), ((totalFreeSpace/1024ll)/1024ll));
} else {
NSLog(#"Error Obtaining System Memory Info: Domain = %#, Code = %#", [error domain], [error code]);
}
return ((totalFreeSpace/1024ll)/1024ll);
}
This will give you total free Space in MB

Base64 Encode File Using NSData Chunks

Update 4
Per Greg's suggestion I've created one pair of image/text that shows the output from a 37k image to base64 encoded, using 100k chunks. Since the file is only 37k it's safe to say the loop only iterated once, so nothing was appended. The other pair shows the output from the same 37k image to base64 encoded, using 10k chunks. Since the file is 37k the loop iterated four times, and data was definitely appended.
Doing a diff on the two files shows that on the 10kb chunk file there's a large difference that begins on line 214 and ends on line 640.
Small Image (37k) - 100k Chunks - Image output
Small Image (37k) - 100k Chunks - Base64 Text output
Small Image (37k) - 10k Chunks - Image output
Small Image (37k) - 10k Chunks - Base64 Text output
Update 3
Here's where my code is now. Cleaned up a bit but still producing the same effect:
// Read data in chunks from the original file
[originalFile seekToEndOfFile];
NSUInteger fileLength = [originalFile offsetInFile];
[originalFile seekToFileOffset:0];
NSUInteger chunkSize = 100 * 1024;
NSUInteger offset = 0;
while(offset < fileLength) {
NSData *chunk = [originalFile readDataOfLength:chunkSize];
offset += chunkSize;
// Convert the chunk to a base64 encoded string and back into NSData
NSString *base64EncodedChunkString = [chunk base64EncodedString];
NSData *base64EncodedChunk = [base64EncodedChunkString dataUsingEncoding:NSASCIIStringEncoding];
// Write the encoded chunk to our output file
[encodedFile writeData:base64EncodedChunk];
// Cleanup
base64EncodedChunkString = nil;
base64EncodedChunk = nil;
// Update progress bar
[self updateProgress:[NSNumber numberWithInt:offset] total:[NSNumber numberWithInt:fileLength]];
}
Update 2
So it looks like files that are larger than 100 KB get scrambled, but files under 100 KB are fine. It's obvious that something is off on my buffer/math/etc, but I'm lost on this one. Might be time to call it a day, but I'd love to go to sleep with this one resolved.
Here's an example:
Update 1
After doing some testing I have found that the same code will work fine for a small image, but will not work for a large image or video of any size. Definitely looks like a buffer issue, right?
Hey there, trying to base64 encode a large file by looping through and doing it one small chunk at a time. Everything seems to work but the files always end up corrupted. I was curious if anyone could point out where I might be going wrong here:
NSFileHandle *originalFile, *encodedFile;
self.localEncodedURL = [NSString stringWithFormat:#"%#-base64.xml", self.localURL];
// Open the original file for reading
originalFile = [NSFileHandle fileHandleForReadingAtPath:self.localURL];
if (originalFile == nil) {
[self performSelectorOnMainThread:#selector(updateStatus:) withObject:#"Encoding failed." waitUntilDone:NO];
return;
}
encodedFile = [NSFileHandle fileHandleForWritingAtPath:self.localEncodedURL];
if (encodedFile == nil) {
[self performSelectorOnMainThread:#selector(updateStatus:) withObject:#"Encoding failed." waitUntilDone:NO];
return;
}
// Read data in chunks from the original file
[originalFile seekToEndOfFile];
NSUInteger length = [originalFile offsetInFile];
[originalFile seekToFileOffset:0];
NSUInteger chunkSize = 100 * 1024;
NSUInteger offset = 0;
do {
NSUInteger thisChunkSize = length - offset > chunkSize ? chunkSize : length - offset;
NSData *chunk = [originalFile readDataOfLength:thisChunkSize];
offset += [chunk length];
NSString *base64EncodedChunkString = [chunk base64EncodedString];
NSData *base64EncodedChunk = [base64EncodedChunkString dataUsingEncoding:NSASCIIStringEncoding];
[encodedFile writeData:base64EncodedChunk];
base64EncodedChunkString = nil;
base64EncodedChunk = nil;
} while (offset < length);
I wish I could give credit to GregInYEG, because his original point about padding was the underlying issue. With base64, each chunk has to be a multiple of 3. So this resolved the issue:
chunkSize = 3600
Once I had that, the corruption went away. But then I ran into memory leak issues, so I added the autorelease pool apprach taken from this post: http://www.cocoadev.com/index.pl?ReadAFilePieceByPiece
Final code:
// Read data in chunks from the original file
[originalFile seekToEndOfFile];
NSUInteger fileLength = [originalFile offsetInFile];
[originalFile seekToFileOffset:0];
// For base64, each chunk *MUST* be a multiple of 3
NSUInteger chunkSize = 24000;
NSUInteger offset = 0;
NSAutoreleasePool *chunkPool = [[NSAutoreleasePool alloc] init];
while(offset < fileLength) {
// Read the next chunk from the input file
[originalFile seekToFileOffset:offset];
NSData *chunk = [originalFile readDataOfLength:chunkSize];
// Update our offset
offset += chunkSize;
// Base64 encode the input chunk
NSData *serializedChunk = [NSPropertyListSerialization dataFromPropertyList:chunk format:NSPropertyListXMLFormat_v1_0 errorDescription:NULL];
NSString *serializedString = [[NSString alloc] initWithData:serializedChunk encoding:NSASCIIStringEncoding];
NSRange r = [serializedString rangeOfString:#"<data>"];
serializedString = [serializedString substringFromIndex:r.location+7];
r = [serializedString rangeOfString:#"</data>"];
serializedString = [serializedString substringToIndex:r.location-1];
// Write the base64 encoded chunk to our output file
NSData *base64EncodedChunk = [serializedString dataUsingEncoding:NSASCIIStringEncoding];
[encodedFile truncateFileAtOffset:[encodedFile seekToEndOfFile]];
[encodedFile writeData:base64EncodedChunk];
// Cleanup
base64EncodedChunk = nil;
serializedChunk = nil;
serializedString = nil;
chunk = nil;
// Update the progress bar
[self updateProgress:[NSNumber numberWithInt:offset] total:[NSNumber numberWithInt:fileLength]];
// Drain and recreate the pool
[chunkPool release];
chunkPool = [[NSAutoreleasePool alloc] init];
}
[chunkPool release];
How are you converting back the base64 data to an image? Some implementations limit the maximum line length they will accept. Try inserting a line break every so many characters.