Loading retina/normal images using core graphics - iphone

+(UIImage*) createCGImageFromFile:(NSString*)inName
{
if(!inName || [inName length]==0)
return NULL;
// use the data provider to get a CGImage; release the data provider
CGImageRef image= NULL;
CGFloat scale = 1.0f;
CGDataProviderRef dataProvider = NULL;
NSString *fileName = nil;
NSString *path = nil;
if([Utilities isRetinaDevice])
{
NSString *extenstion = [inName pathExtension];
NSString *stringWithoutPath = [inName stringByDeletingPathExtension];
fileName = [NSString stringWithFormat:#"/Images/%##2x.%#",stringWithoutPath,extenstion];
path = [[[NSBundle mainBundle] resourcePath] stringByAppendingString:fileName];
dataProvider = CGDataProviderCreateWithFilename([path UTF8String]);
if(dataProvider)
{
image = CGImageCreateWithPNGDataProvider(dataProvider, NULL, NO,kCGRenderingIntentDefault);
CGDataProviderRelease(dataProvider);
if(image)
{
scale = 2.0f;
}
}
}
if(image == NULL) //Try normal image
{
fileName = [NSString stringWithFormat:#"/Images/%#",inName];
path = [[[NSBundle mainBundle] resourcePath] stringByAppendingString:fileName];
dataProvider = CGDataProviderCreateWithFilename([path UTF8String]);
if(dataProvider)
{
image = CGImageCreateWithPNGDataProvider(dataProvider, NULL, NO,kCGRenderingIntentDefault);
CGDataProviderRelease(dataProvider);
}
}
// make a bitmap context of a suitable size to draw to, forcing decode
size_t width = CGImageGetWidth(image);
size_t height = CGImageGetHeight(image);
unsigned char *imageBuffer = (unsigned char *)malloc(width*height*4);
CGColorSpaceRef colourSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef imageContext =
CGBitmapContextCreate(imageBuffer, width, height, 8, width*4, colourSpace,
kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Little);
CGColorSpaceRelease(colourSpace);
// draw the image to the context, release it
CGContextDrawImage(imageContext, CGRectMake(0, 0, width, height), image);
CGImageRelease(image);
// now get an image ref from the context
CGImageRef cgImageRef = CGBitmapContextCreateImage(imageContext);
CGContextRelease(imageContext);
free(imageBuffer);
UIImage *outImage = nil;
if(cgImageRef)
{
outImage = [[UIImage alloc] initWithCGImage:cgImageRef scale:scale orientation:UIImageOrientationUp];
CGImageRelease(cgImageRef);
}
return outImage;//Need to release by the receiver
}
Earlier the function was not loading retina images since i was not appending #2x to the image path, i thought that apple itself will take care.
Now I modified the code to load #2x images and created UIImage using scale factor, am I missing something in above code? Is it fine?
P.S. Currently I am not facing any problem using this code but I am not 100% sure

I would use UIImage to load it (it will correct the path)
That will greatly simply your code
THEN get a CGImage from it (UIImage is basically just a thin wrapper around CG so thats no real overhead)
then proceed with your code - it looks ok to me

Related

Faster way to load Images in Collection View

I have a little flow problem with my UICollectionView. I want to display PDF's thumbnails just like in Apple's iBook app, when I scroll my collection view I can see it's not really smooth. Here is the way I use to load my pictures :
- (UICollectionViewCell *)collectionView:(UICollectionView *)cv cellForItemAtIndexPath: (NSIndexPath *)indexPath
{
GridCell *cell = [cv dequeueReusableCellWithReuseIdentifier:#"gridCell" forIndexPath:indexPath];
...
// Set Thumbnail
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
if ([Tools checkIfLocalFileExist:cell.pdfDoc])
{
UIImage *thumbnail = [Tools generateThumbnailForFile:((PDFDocument *)[self.pdfList objectAtIndex:indexPath.row]).title];
dispatch_async( dispatch_get_main_queue(), ^{
[cell.imageView setImage:thumbnail];
});
}
});
...
return cell;
}
Method to get thumbnail :
+ (UIImage *)generateThumbnailForFile:(NSString *) fileName
{
// ----- Check if thumbnail already exist
NSString* documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString* thumbnailAddress = [documentsPath stringByAppendingPathComponent:[[fileName stringByDeletingPathExtension] stringByAppendingString:#".png"]];
BOOL fileExists = [[NSFileManager defaultManager] fileExistsAtPath:thumbnailAddress];
if (fileExists)
return [UIImage imageWithContentsOfFile:thumbnailAddress];
// ----- Generate Thumbnail
NSString* filePath = [documentsPath stringByAppendingPathComponent:fileName];
CFURLRef url = (__bridge CFURLRef)[NSURL fileURLWithPath:filePath];
CGPDFDocumentRef documentRef = CGPDFDocumentCreateWithURL(url);
CGPDFPageRef pageRef = CGPDFDocumentGetPage(documentRef, 1);
CGRect pageRect = CGPDFPageGetBoxRect(pageRef, kCGPDFCropBox);
UIGraphicsBeginImageContext(pageRect.size);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextTranslateCTM(context, CGRectGetMinX(pageRect),CGRectGetMaxY(pageRect));
CGContextScaleCTM(context, 1, -1);
CGContextTranslateCTM(context, -(pageRect.origin.x), -(pageRect.origin.y));
CGContextDrawPDFPage(context, pageRef);
// ----- Save Image
UIImage *finalImage = UIGraphicsGetImageFromCurrentImageContext();
NSData *imageData = [NSData dataWithData:UIImagePNGRepresentation(finalImage)];
[imageData writeToFile:thumbnailAddress atomically:YES];
UIGraphicsEndImageContext();
return finalImage;
}
Do you have any suggestion ?
Take a look at https://github.com/rs/SDWebImage. The library works great for async image loading particularly the method setImageWithURL:placeholderImage:
You can call that method and set a place holder with a loading image or blank png and once the image you are trying to retrieve is loaded it will fill in the place holder. This should speed up your app quite a bit.

image size on device is different after uploaing image on server

I am doing following operation before uploading image. If I check size of image before uploading and after uploading it gets double(i.e if I have uploaded 2 MB image I can see 4 MB image on server).
NSTimeInterval timeInterval = [assetDate timeIntervalSince1970];
ALAssetRepresentation *rep = [temp defaultRepresentation];
CGImageRef iref = [rep fullResolutionImage];
StrPath = [StrPath stringByAppendingFormat:#"%d.%#",(int)timeInterval,strImageType];
UIImage *image =[UIImage imageWithCGImage:iref scale:[rep scale] orientation:(UIImageOrientation)[rep orientation]];
NSData *dataObj = nil;
dataObj = UIImageJPEGRepresentation(image, 1.0);
NSString* StrFileData = [Base64 encode:dataObj];
NSString* strFileHash = [dataObj md5Test];
Use following method for specific hight and width with image
+ (UIImage*)resizeImage:(UIImage*)image withWidth:(int)width withHeight:(int)height
{
CGSize newSize = CGSizeMake(width, height);
float widthRatio = newSize.width/image.size.width;
float heightRatio = newSize.height/image.size.height;
if(widthRatio > heightRatio)
{
newSize=CGSizeMake(image.size.width*heightRatio,image.size.height*heightRatio);
}
else
{
newSize=CGSizeMake(image.size.width*widthRatio,image.size.height*widthRatio);
}
UIGraphicsBeginImageContextWithOptions(newSize, NO, 0.0);
[image drawInRect:CGRectMake(0,0,newSize.width,newSize.height)];
UIImage* newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return newImage;
}
This method return NewImage, It will not be resiz :)
I have few suggestions. If you note these.. It should help you
You can get the ALAsset image NSData directly from ALAsset defaultRepresentation
getBytes:fromOffset:length:error: method.
Use ALAsset thumbnail image instead of retrieving full resolution image
CGImageRef iref = [myasset aspectRatioThumbnail];
ALAsset Libraay block will execute in seperate thread. So do the server upload in main thread
dispatch_async(dispatch_get_global_queue(0, 0), ^{
// do the server upload
});

Crash on CFDataGetBytes(image, CFRangeMake(0, CFDataGetLength(image)), destPixels);

I am making video of screen but crashes on this line.
CFDataGetBytes(image, CFRangeMake(0, CFDataGetLength(image)), destPixels);
Note: It will work if the pixel buffer is contiguous and has the same bytesPerRow as the input data
So I am providing my code that grabs frames from the camera - maybe it will help After grabbing the data, I put it on a queue for further processing. I had to remove some of the code as it was not relevant to you - so what you see here has pieces you should be able to use.
- (void)captureOutput:(AVCaptureVideoDataOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection
{
#autoreleasepool {
CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
//NSLog(#"PE: value=%lld timeScale=%d flags=%x", prStamp.value, prStamp.timescale, prStamp.flags);
/*Lock the image buffer*/
CVPixelBufferLockBaseAddress(imageBuffer,0);
NSRange captureRange;
/*Get information about the image*/
uint8_t *baseAddress = (uint8_t *)CVPixelBufferGetBaseAddress(imageBuffer);
size_t bytesPerRow = CVPixelBufferGetBytesPerRow(imageBuffer);
size_t width = CVPixelBufferGetWidth(imageBuffer);
// Note Apple sample code cheats big time - the phone is big endian so this reverses the "apparent" order of bytes
CGContextRef newContext = CGBitmapContextCreate(NULL, width, captureRange.length, 8, bytesPerRow, colorSpace, kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrder32Little); // Video in ARGB format
assert(newContext);
uint8_t *newPtr = (uint8_t *)CGBitmapContextGetData(newContext);
size_t offset = captureRange.location * bytesPerRow;
memcpy(newPtr, baseAddress + offset, captureRange.length * bytesPerRow);
CVPixelBufferUnlockBaseAddress(imageBuffer, 0);
CMTime prStamp = CMSampleBufferGetPresentationTimeStamp(sampleBuffer); // when it was taken?
//CMTime deStamp = CMSampleBufferGetDecodeTimeStamp(sampleBuffer); // now?
NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:
[NSValue valueWithBytes:&saveState objCType:#encode(saveImages)], kState,
[NSValue valueWithNonretainedObject:(__bridge id)newContext], kImageContext,
[NSValue valueWithBytes:&prStamp objCType:#encode(CMTime)], kPresTime,
nil ];
dispatch_async(imageQueue, ^
{
// could be on any thread now
OSAtomicDecrement32(&queueDepth);
if(!isCancelled) {
saveImages state; [(NSValue *)[dict objectForKey:kState] getValue:&state];
CGContextRef context; [(NSValue *)[dict objectForKey:kImageContext] getValue:&context];
CMTime stamp; [(NSValue *)[dict objectForKey:kPresTime] getValue:&stamp];
CGImageRef newImageRef = CGBitmapContextCreateImage(context);
CGContextRelease(context);
UIImageOrientation orient = state == saveOne ? UIImageOrientationLeft : UIImageOrientationUp;
UIImage *image = [UIImage imageWithCGImage:newImageRef scale:1.0 orientation:orient]; // imageWithCGImage: UIImageOrientationUp UIImageOrientationLeft
CGImageRelease(newImageRef);
NSData *data = UIImagePNGRepresentation(image);
// NSLog(#"STATE:[%d]: value=%lld timeScale=%d flags=%x", state, stamp.value, stamp.timescale, stamp.flags);
{
NSString *name = [NSString stringWithFormat:#"%d.png", num];
NSString *path = [[wlAppDelegate snippetsDirectory] stringByAppendingPathComponent:name];
BOOL ret = [data writeToFile:path atomically:NO];
//NSLog(#"WROTE %d err=%d w/time %f path:%#", num, ret, (double)stamp.value/(double)stamp.timescale, path);
if(!ret) {
++errors;
} else {
dispatch_async(dispatch_get_main_queue(), ^
{
if(num) [delegate progress:(CGFloat)num/(CGFloat)(MORE_THAN_ONE_REV * SNAPS_PER_SEC) file:path];
} );
}
++num;
}
} else NSLog(#"CANCELLED");
} );
}
}

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

NSData -> UIImage -> NSData

I have an NSData object, which contains RGB values for an image. I want to turn that into a UIImage (given the width and the height). Then I want to convert that UIImage back into an NSData object identical to the one I started with.
Please help me I've been trying for hours now.
Here are some things I've looked at/tried though probably didn't too them right cause it didn't work:
CGImageCreate
CGBitmapContextCreateWithData
CGBitmapContextGetData
CGDataProviderCopyData(CGImageGetDataProvider(imageRef))
Thanks!
Here is my current code:
NSMutableData *rgb; //made earlier
double len = (double)[rgb length];
len /= 3;
len += 0.5;
len = (int)len;
int diff = len*3-[rgb length];
NSString *str = #"a";
NSData *a = [str dataUsingEncoding:NSUTF8StringEncoding];
for(int i =0; i < diff; i++) {
[toEncode appendData:a]; //so if my data is RGBRGBR it will turn into RGBRGBR(97)(97)
}
size_t width = (size_t)len;
size_t height = 1;
CGContextRef ctx;
CFDataRef m_DataRef;
m_DataRef = (__bridge CFDataRef)toEncode;
UInt8 * m_PixelBuf = (UInt8 *) CFDataGetBytePtr(m_DataRef);
vImage_Buffer src;
src.data = m_PixelBuf;
src.width = width;
src.height = height;
src.rowBytes = 3 * width;
vImage_Buffer dst;
dst.width = width;
dst.height = height;
dst.rowBytes = 4 * width;
vImageConvert_RGB888toARGB8888(&src, NULL, 0, &dst, NO, kvImageNoFlags);
// free(m_PixelBuf);
// m_PixelBuf = dst.data;
// NSUInteger lenB = len * (4/3);
/*
UInt8 * m_Pixel = malloc(sizeof(UInt8) * lenB);
int z = 0;
for(int i = 0; i < lenB; i++) {
if(i % 4==0) {
m_Pixel[i] = 0;
} else {
m_Pixel[i] = m_PixelBuf[z];
z++;
}
}*/
// Byte tmpByte;
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
/*
ctx = CGBitmapContextCreate(m_PixelBuf,
width,
height,
8,
4*width,
colorSpace,
kCGImageAlphaPremultipliedFirst );
*/
size_t w = (size_t)len;
ctx = CGBitmapContextCreate(dst.data,
w,
height,
8,
4*width,
colorSpace,
kCGImageAlphaNoneSkipFirst );
CGImageRef imageRef = CGBitmapContextCreateImage (ctx);
UIImage* rawImage = [UIImage imageWithCGImage:imageRef];
CGContextRelease(ctx);
I get this error:<Error>: CGBitmapContextCreate: invalid data bytes/row: should be at least 324 for 8 integer bits/component, 3 components, kCGImageAlphaNoneSkipFirst.
TO UIImage from NSData:
[UIImage imageWithData:]
More on UIImage
TO NSData from UIImage:
UIImage *img = [UIImage imageNamed:#"some.png"];
NSData *dataObj = UIImageJPEGRepresentation(img, 1.0);
More on UIImageJPEGRepresentation()
The basic procedure would be to create a bitmap context using CGBitmapContextCreateWithData and then creating a CGImageRef from that with CGBitmapContextCreateImage. The parameters for creating the bitmap context depend on how your raw data is laid out in memory. Not all kinds of raw data are supported by Quartz.
The documentation on CGBitmapContextCreateWithData is quite detailed, and this is the most challenging part, getting the CGImageRef from the context and wrapping that in a UIImage (imageWithCGImage:) is trivial afterwards.
If your data is in RGB format, you will want to create a bitmap using CGBitmapContextCreate with CGColorSpaceCreateDeviceRGB and using kCGImageAlphaNone.
The error stating that rowBytes needs to be at least 324; dividing that by 4 is 81 which implies that 'width' is smaller than 'w', and that w=81. The two values should match.
Try replacing width and w with a small number like 5 to validate this. I would also note that you should be allocating dst.data via malloc prior to calling vImageConvert_RGB888toARGB8888.
Consider using CGImageCreate() instead of creating a bitmapcontext:
// this will automatically free() dst.data when destData is dealloc
NSData *destData = [NSData dataWithBytesNoCopy:dst.data length:4*width*height];
CGDataProviderRef provider = CGDataProviderCreateWithCFData((__bridge CFDataRef)destData);
CGImageRef imageRef = CGImageCreate(width,
height,
8, //bits per component
8*4, //bits per pixel
4*width, //bytesPerRow
colorSpace, //colorspace
kCGImageAlphaNoneSkipFirst,
provider, //CGDataProviderRef
NULL, //decode
false, //should interpolate
kCGRenderingIntentDefault //intent
);