How to save and retrieve UIImages using "imageWithData" maintaining proper image format? - uiimagejpegrepresentation

I'm working on creating and storing OpenGL ES1 3D models, and want to include image files to be used as textures, within the same file as the 3D model data. I am having trouble loading the image data in a usable format. I'm using UIImageJPEGRepresentation to convert the image data and store it into an NSData. I then append it to a NSMutableData object, along with all the 3D data, and write it out to a file. The data seems to write and read without error, but I encounter problems when trying use the image data to create a "CGImageRef" which I use to generate the texture data for the 3D model. The image data seems to be in an unrecognized format after it is loaded from the file, because it generates the error "CGContextDrawImage: invalid context 0x0.” when I attempt to create the "CGImageRef". I suspect that the image data is gettin misaligned somehow, causing it to be rejected when attempting to create the "CGImageRef". I appreciate any help. I'm stumped at this point. All of the data sizes and offsets add up and look fine. Saves and loads happen without error. The image data just seems off a bit, but I don't know why.
Here's my code:
//======================================================
- (BOOL)save3DFile: (NSString *)filePath {
// load TEST IMAGE into UIIMAGE
UIImage *image = [UIImage imageNamed:#“testImage.jpg"];
// convert image to JPEG encoded NSDATA
NSData *imageData = UIImageJPEGRepresentation(image,1.0);
// Save length of imageData to global "imDataLen" to use later in “load3DFile”
imDataLen = [imageData length];
// TEST: this works fine for CGImageRef creation in “loadTexture”
// traceView.image=[UIImage imageWithData:[imageData subdataWithRange:NSMakeRange(0, imageDataLen)]];
// [self loadTexture];
// TEST: this also works fine for CGImageRef creation in “loadTexture”
// traceView.image=[UIImage imageWithData:txImData];
// [self loadTexture];
fvoh.fileVersion = FVO_VERSION;
fvoh.obVertDatLen = obVertDatLen;
fvoh.obFaceDatLen = obFaceDatLen;
fvoh.obNormDatLen = obNormDatLen;
fvoh.obTextDatLen = obTextDatLen;
fvoh.obCompCount = obCompCount;
fvoh.obVertCount = obVertCount;
fvoh.obElemCount = obElemCount;
fvoh.obElemSize = obElemSize;
fvoh.obElemType = obElemType;
NSMutableData *obSvData;
obSvData=[NSMutableData dataWithBytes:&fvoh length:(sizeof(fvoh))];
[obSvData appendBytes:obElem length:obFaceDatLen];
[obSvData appendBytes:mvElem length:obVertDatLen];
[obSvData appendBytes:mvNorm length:obNormDatLen];
[obSvData appendBytes:obText length:obTextDatLen];
[obSvData appendBytes:&ds length:(sizeof(ds))];
// next, we append image data, and write all data to a file
// seems to work fine, no errors, at this point
[obSvData appendBytes: imageData length:[imageData length]];
BOOL success=[obSvData writeToFile: filePath atomically:YES];
return success;
}
//======================================================
- (void) load3DFile:(NSString *)filePath {
NSData *fvoData;
NSUInteger offSet,fiLen,fhLen,dsLen;
[[FileList sharedFileList] setCurrFile:(NSString *)filePath];
fvoData=[NSData dataWithContentsOfFile:filePath];
fiLen=[fvoData length];
fhLen=sizeof(fvoh);
dsLen=sizeof(ds);
memcpy(&fvoh,[fvoData bytes],fhLen);offSet=fhLen;
//+++++++++++++++++++++++++++++++
obVertDatLen = fvoh.obVertDatLen;
obFaceDatLen = fvoh.obFaceDatLen;
obNormDatLen = fvoh.obNormDatLen;
obTextDatLen = fvoh.obTextDatLen;
obCompCount = fvoh.obCompCount;
obVertCount = fvoh.obVertCount;
obElemCount = fvoh.obElemCount;
obElemSize = fvoh.obElemSize;
obElemType = fvoh.obElemType;
//+++++++++++++++++++++++++++++++
memcpy(obElem, [fvoData bytes]+offSet,obFaceDatLen);offSet+=obFaceDatLen;
memcpy(mvElem, [fvoData bytes]+offSet,obVertDatLen);offSet+=obVertDatLen;
memcpy(mvNorm, [fvoData bytes]+offSet,obNormDatLen);offSet+=obNormDatLen;
memcpy(obText, [fvoData bytes]+offSet,obTextDatLen);offSet+=obTextDatLen;
memcpy(&ds, [fvoData bytes]+offSet,dsLen);offSet+=dsLen;
// the following seem to read the data into “imageData” just fine, no errors
// NSData *imageData = [fvoData subdataWithRange:NSMakeRange(offSet, imDataLen)];
// NSData *imageData = [fvoData subdataWithRange:NSMakeRange((fiLen-imDataLen), imDataLen)];
// NSData *imageData = [NSData dataWithBytes:[fvoData bytes]+offSet length: imDataLen];
NSData *imageData = [NSData dataWithBytes:[fvoData bytes]+(fiLen-imDataLen) length: imDataLen];
// but the contents of imageData seem to end up in an unexpected format, causing error:
// “CGContextDrawImage: invalid context 0x0.” during CGImageRef creation in “loadTexture”
traceView.image=[UIImage imageWithData:imageData];
[self loadTexture];
}
//======================================================
- (void)loadTexture {
CGImageRef image=[traceView.image].CGImage;
CGContextRef texContext;GLubyte* bytes=nil;GLsizei width,height;
if(image){
width=(GLsizei)CGImageGetWidth(image);
height=(GLsizei)CGImageGetHeight(image);
bytes=(GLubyte*) calloc(width*height*4,sizeof(GLubyte));
texContext=CGBitmapContextCreate(bytes,width,height,8,width*4,CGImageGetColorSpace(image),
kCGImageAlphaPremultipliedLast);
CGContextDrawImage(texContext,CGRectMake(0.0,0.0,(CGFloat)width,(CGFloat)height),image);
CGContextRelease(texContext);
}
if(bytes){
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_CLAMP_TO_EDGE);
glTexImage2D(GL_TEXTURE_2D,0,GL_RGBA,width,height,0,GL_RGBA,GL_UNSIGNED_BYTE,bytes);
free(bytes);
}
}
//======================================================

I failed to receive any answers to this question. I finally stumbled across the answer myself. When I execute the save3DFile code, instead of adding the image data to NSMutableData *obSvData, using 'appendBytes' as illustrated below:
[obSvData appendBytes: imageData length:[imageData length]];
I instead use 'appendData' as shown here:
[obSvData appendData: imageData];
where imageData was previously filled with the contents of a UIImage and converted to JPEG format in the process as follows:
NSData *imageData = UIImageJPEGRepresentation(image,1.0);
See the complete code listing above for context. Anyway, the using 'appendData' instead of 'appendBytes' made all the difference, and allowed me to store the image data in the same file along with all the other 3D model data (vertices, indices, normals, et cetera), reloading all that data without problem, and successfully create 3D models with textures from a single file.

Related

image uploaded rotated other way

let image_data = UIImageJPEGRepresentation(self.imagetoadd.image!,0.0)
The image in ios, am using swift 3 to do this is being uploaded rotated.How can I solve such thing?
JPEG images usually contain an EXIF dictionary, here are stored a lot information about how the image was taken, image rotation is one of it.
UIImage instances keeps these information (if the original image has it) as well inside a specific property called imageOrientation.
As far as I remember this information is ripped of by using the method UIImageJPEGRepresentation.
To create a correct data instance with the above information you must use Core Graphics methods, or normalize the rotation before sending the image.
To normalize the image something like that should be enough:
CGImageRef cgRef = imageToSave.CGImage;
UIImage * fixImage = [[UIImage alloc] initWithCGImage:cgRef scale:imageToSave.scale orientation:UIImageOrientationUp];
To keep the rotation information:
CFURLRef url = (__bridge_retained CFURLRef)[NSURL fileURLWithPath:path];//Save data path
NSDictionary * metadataDictionary = [self imageMetadataForPath:pathToOriginalImage];
CFMutableDictionaryRef metadataImage = (__bridge_retained CFMutableDictionaryRef) metadata;
CGImageDestinationRef destination = CGImageDestinationCreateWithURL(url, kUTTypeJPEG, 1, NULL);
CGImageDestinationAddImage(destination, image, metadataImage);
if (!CGImageDestinationFinalize(destination)) {
DLog(#"Failed to write image to %#", path);
}
Where the -imageMetadataForPath:
- (NSDictionary*) imageMetadataForPath:(NSString*) imagePath{
NSURL *imageURL = [NSURL fileURLWithPath:imagePath];
CGImageSourceRef mySourceRef = CGImageSourceCreateWithURL((__bridge CFURLRef)imageURL, NULL);
NSDictionary * dict = (NSDictionary *) CFBridgingRelease(CGImageSourceCopyPropertiesAtIndex(mySourceRef,0,NULL));
CFRelease(mySourceRef);
return dict;
}
This is a copy and paste from a project of mine, you probably need to do a huge refactoring, also because it is using manual memory management in core foundation and you are using SWIFT. Of course by using this last set of instructions, the backend code must be prepared to deal with image orientation too.
If you want to know more about rotation, here is a link.

Is it possible to use NSKeyedArchiver to store an animated UIImage?

I loaded an image using frames downloaded from a web server.
NSArray* frames = [self fetchFramesFromServer];
imageView.image = [UIImage animatedImageWithImages:frames duration:1.0];
Whenever I try to archive these frames, the encoding function returns NO:
BOOL success = [NSKeyedArchiver archiveRootObject:(frames) toFile:archivePath];
Whenever I try to archive the image, the encoding function also returns NO:
BOOL success = [NSKeyedArchiver archiveRootObject:(imageView.image) toFile:archivePath];
Is this even possible, or am I missing some caveat of archiveRootObject that requires the UIImage to be loaded from the bundle, in png-format, or non-animated?
Just FYI, the archivePath when printed out in the debugger is
NSPathStore2 * #"/var/mobile/Containers/Data/Application/E475E3C1-4E3F-43D6-AD66-0F98320CF279/Library/Private Documents/assets/a/b/c.archive" 0x000000012c555a60
UPDATE:
I am storing the individual frames of the animation inside a for-loop, but I get the following:
+ (void) saveImage:(UIImage*)inputImage
withSuffix:(NSString*)fileSuffix
{
if (inputImage == nil) {
NSLog(#"ERROR: input image is nil!");
return;
}
// strip away intermediate folders to avoid over-nesting
// ex: __F1/MF/FR/1_MF_FR_Animation_a1.png
// ===> 1_MF_FR_Animation_a1.png
NSString* suffixStub = [MyDatabase stubOfFilepath:fileSuffix];
NSURL* url = [MyDatabase folderURL];
NSURL* imageURL = [url URLByAppendingPathComponent:suffixStub];
NSData* pngData = UIImagePNGRepresentation(inputImage);
[NSKeyedArchiver archiveRootObject:pngData toFile:imageURL.path];
pngData = nil;
NSLog(#"Image %# successfully saved!", fileSuffix);
}
But on the NSKeyedArchiver line, I get "malloc: *** error for object 0x178202340: Freeing unallocated pointer"
First convert your image to NSData and then try archiving it
NSData* data = UIImagePNGRepresentation(imageView.image);
BOOL success = [NSKeyedArchiver archiveRootObject:data toFile:archivePath];
In case of NSArray, archiving is possible only if all the objects of array conforms to NSCoding protocol. If your array has custom object refer Custom object archiving on how to conform to NSCoding.
UPDATE
If objects of your array are frames of type CGRect the you can use NSStringFromCGRect() and CGRectFromString() to store and then retrieve them.

How to get UIImage(ImageURL) height and width without converting to NSData

In my project i need to show the different sizes of images in zig-zag fashion. so, i converted the uiimages(url) which are coming from service to NSData and then i get the uiimage. my code is
NSURL *url = [NSURL URLWithString:[[_result objectAtIndex:i ] valueForKey:#"PImage"]];
NSData *data = [NSData dataWithContentsOfURL:url];
UIImage *image = [UIImage imageWithData:data];
so i can get the image size(width and height), But my problem is according to the image size, i need to create UIView, this code is works fine for me, but it is taking too much of time(almost 25 sec) to load 8 images. i figured converting UIImage to NSData is taking time. Is there any way to get the image size(width and height) without converting it into NSData
Thanks for spending time for me.
You can get image properties without actually loading whole image data from disk using ImageIO framework:
#import ImageIO;
...
NSURL *imageURL = … // Init URL somehow
CGImageSourceRef imgSource = CGImageSourceCreateWithURL((__bridge CFURLRef)url, NULL);
NSDictionary* imageProps = (__bridge_transfer NSDictionary*) CGImageSourceCopyPropertiesAtIndex(imgSource, 0, NULL);
NSLog(#"%#", imageProps);
CFRelease(imgSource);
Image width and height will be stored in dictionary under PixelHeight and PixelWidth keys (tested with png image, may be other image formats will use different keys)
Instead of converting url to data and to UIImage, Use EGOImageView OR AsyncImageView. You can simply pass the URL to them. Again setFrame based on size of the image.

Converting NSData to CGImage and then back to NSData makes the file too big

I have built a camera using AVFoundation.
Once my AVCaptureStillImageOutput has completed its captureStillImageAsynchronouslyFromConnection:completionHandler: method, I create a NSData object like this:
NSData *imageData = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:imageDataSampleBuffer];
Once I have the NSData object, I would like to rotate the image -without- converting to a UIImage. I have found out that I can convert to a CGImage to do so.
After I have the imageData, I start the process of converting to CGImage, but I have found that the CGImageRef ends up being THIRTY times larger than the NSData object.
Here is the code I use to convert to CGImage from NSData:
CGDataProviderRef imgDataProvider = CGDataProviderCreateWithCFData((__bridge CFDataRef)(imageData));
CGImageRef imageRef = CGImageCreateWithJPEGDataProvider(imgDataProvider, NULL, true, kCGRenderingIntentDefault);
If I try to NSLog out the size of the image, it comes to 30 megabytes when the NSData was a 1.5-2 megabyte image!
size_t imageSize = CGImageGetBytesPerRow(imageRef) * CGImageGetHeight(imageRef);
NSLog(#"cgimage size = %zu",imageSize);
I thought that maybe when you go from NSData to CGImage, the image decompresses, and then maybe if I converted back to NSData, that it might go back to the right file size.
imageData = (NSData *) CFBridgingRelease(CGDataProviderCopyData(CGImageGetDataProvider(imageRef)));
The above NSData has the same length as the CGImageRef object.
If I try to save the image, the image is a 30mb image that cannot be opened.
I am totally new to using CGImage, so I am not sure if I am converting from NSData to CGImage and back incorrectly, or if I need to call some method to decompress again.
Thanks in advance,
Will
I was doing some image manipulation and came across your question on SO. Seems like no one else came up with an answer, so here's my theory.
While it's theoretically possible to convert a CGImageRef back to NSData in the manner that you described, the data itself is invalid and not a real JPEG or PNG, as you discovered by it not being readable. So I don't think that the NSData.length is correct. You have to actually jump through a number of steps to recreate an NSData representation of a CGImageRef:
// incoming image data
NSData *image;
// create the image ref
CGDataProviderRef imgDataProvider = CGDataProviderCreateWithCFData((__bridge CFDataRef) image);
CGImageRef imageRef = CGImageCreateWithJPEGDataProvider(imgDataProvider, NULL, true, kCGRenderingIntentDefault);
// image metadata properties (EXIF, GPS, TIFF, etc)
NSDictionary *properties;
// create the new output data
CFMutableDataRef newImageData = CFDataCreateMutable(NULL, 0);
// my code assumes JPEG type since the input is from the iOS device camera
CFStringRef type = UTTypeCreatePreferredIdentifierForTag(kUTTagClassMIMEType, (__bridge CFStringRef) #"image/jpg", kUTTypeImage);
// create the destination
CGImageDestinationRef destination = CGImageDestinationCreateWithData(newImageData, type, 1, NULL);
// add the image to the destination
CGImageDestinationAddImage(destination, imageRef, (__bridge CFDictionaryRef) properties);
// finalize the write
CGImageDestinationFinalize(destination);
// memory cleanup
CGDataProviderRelease(imgDataProvider);
CGImageRelease(imageRef);
CFRelease(type);
CFRelease(destination);
NSData *newImage = (__bridge_transfer NSData *)newImageData;
With these steps, the newImage.length should be the same as image.length. I haven't tested since I actually do cropping between the input and the output, but based on the crop, the size is roughly what I expected (the output is roughly half the pixels of the input and thus the output length roughly half the size of the input length).
if somebody is looking for swift version of "Covert CGImage to Data" , Here it is :
extension CGImage {
var jpegData: Data? {
guard let mutableData = CFDataCreateMutable(nil, 0),
let destination = CGImageDestinationCreateWithData(mutableData, kUTTypeJPEG, 1, nil)
else {
return nil
}
CGImageDestinationAddImage(destination, self, nil)
guard CGImageDestinationFinalize(destination) else { return nil }
return mutableData as Data
}
}

cached NSData object can't be used after retrieval

I am caching an NSData object containing image data retrieved from the web. The image displays correctly before caching. When I retrieve the data object from the cache, the data can no longer be used to create a UIImage, even though the data objects are identical.
Please see the relevant snippets of my code below
NSData *webData= [NSData dataWithContentsOfURL:webPath]; //retrieve from web
UIImage *webImage = [UIImage imageWithData:webData]; //works fine
[webData writeToURL:filePath atomically:YES]; //cache
NSData *cacheData = [NSData dataWithContentsOfURL:filePath]; //get from cache
if ([cacheData isEqualToData:webData]) NSLog(#"Equal"); //Data objects are equal
UIImage *cacheImage = [UIImage imageWithData:cacheData]; //cacheImage is nil
I can fix the problem by changing the way I store my data to the cache
NSData *temp = UIImageJPEGRepresentation(webImage, 1.0):
[temp writeToURL:filePath atomically:YES];
Now the webData and cacheData are no longer equal, but cacheImage is not nil and displays properly.
EDIT - After a bit more testing, I realized I get the same problem using UIImageJPEGRepresentation as well.
Anyone know why this would be?
Thanks.
Figure out the problem was that I was trying to do all this before my view controller was fully loaded.

Resources