0

I need to generate and save 320 images as PNGs when the game is first run. These images will then be loaded instead of being generated again. Here is the process:

  • load image template (black and white with alpha)
  • overlay non transparent pixels with specified colour
  • put on top the template at 0.3 opacity merging it to one final image
  • return back UIImage
  • store the UIImage, converted to NSData to PNG in Cache directory

This is done using UIGraphicsBeginImageContextWithOptions. This process needs to be done for 32 image templates in 10 colours on the background thread. The purpose is that these will be used as avatar/profile images in this game, scaled down at certain screens as appropriate. They cannot be generated every time though, because this causes too much lag.

The images are 400x400 each. They result being about 20/25 kB each when stored. When I try to use my current way of generating and storing, I get a memory warning and I see (using Instruments) that the number of alive CGImage and UIImage objects keeps increasing rapidly. This seems like they're being retained but I don't hold any references to them.

Here is my other question closer detailing the code I'm using: UIGraphicsBeginImageContext created image

What is the best way to create and store to secondary storage this many images? Thanks in advance.

Edit:

Here's the whole code I currently use to create and save the images:

//==========================================================
// Definitions and Macros
//==========================================================

//HEX color macro
#define UIColorFromRGB(rgbValue) [UIColor \
colorWithRed:((float)((rgbValue & 0xFF0000) >> 16))/255.0 \
green:((float)((rgbValue & 0xFF00) >> 8))/255.0 \
blue:((float)(rgbValue & 0xFF))/255.0 alpha:1.0]

//Colours
#define RED_COLOUR UIColorFromRGB(0xF65D58)
#define ORANGE_COLOUR UIColorFromRGB(0xFF8D16)
#define YELLOW_COLOUR UIColorFromRGB(0xFFD100)
#define LIGHT_GREEN_COLOUR UIColorFromRGB(0x82DE13)
#define DARK_GREEN_COLOUR UIColorFromRGB(0x67B74F)
#define TURQUOISE_COLOUR UIColorFromRGB(0x32ADA6)
#define LIGHT_BLUE_COLOUR UIColorFromRGB(0x11C9FF)
#define DARK_BLUE_COLOUR UIColorFromRGB(0x2E97F5)
#define PURPLE_COLOUR UIColorFromRGB(0x8F73FD)
#define PINK_COLOUR UIColorFromRGB(0xF35991)



#import "ViewController.h"

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];

    //Generate the graphics
    [self generateAndSaveGraphics];

}




//==========================================================
// Generating and Saving Graphics
//==========================================================

-(void)generateAndSaveGraphics {

    dispatch_async( dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

        [self createAvatarImages];

        //Here create all other images that need to be saved to Cache directory


        dispatch_async( dispatch_get_main_queue(), ^{ //Finished

            NSLog(@"DONE"); //always runs out of memory before getting here
        });

    });
}

-(void)createAvatarImages {

    //Create avatar images
    NSArray *colours = [NSArray arrayWithObjects:RED_COLOUR, ORANGE_COLOUR, YELLOW_COLOUR, LIGHT_GREEN_COLOUR, DARK_GREEN_COLOUR, TURQUOISE_COLOUR, LIGHT_BLUE_COLOUR, DARK_BLUE_COLOUR, PURPLE_COLOUR, PINK_COLOUR, nil];

    NSString *cacheDir = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];


    for(int i = 0; i < 32; i++) { //Avatar image templates are named m1 - m16 and f1 - f16

        NSString *avatarImageName;

        if(i < 16) { //female avatars

            avatarImageName = [NSString stringWithFormat:@"f%i", i+1];
        }
        else { //male avatars

            avatarImageName = [NSString stringWithFormat:@"m%i", i-15];
        }


        for(int j = 0; j < colours.count; j++) { //make avatar image for each colour

            @autoreleasepool { //only helps very slightly

                UIColor *colour = [colours objectAtIndex:j];
                UIImage *avatarImage = [self tintedImageFromImage:[UIImage imageNamed:avatarImageName] colour:colour intensity:0.3];

                NSString *fileName = [NSString stringWithFormat:@"%@_%i.png", avatarImageName, j];
                NSString *filePath = [cacheDir stringByAppendingPathComponent:fileName];

                NSData *imageData = [NSData dataWithData:UIImagePNGRepresentation(avatarImage)];
                [imageData writeToFile:filePath atomically:YES];

                NSLog(@"AVATAR IMAGE CREATED");

            }

        }
    }
}




//==========================================================
// Universal Image Tinting Code
//==========================================================

//Creates a tinted image based on the source greyscale image and tinting intensity
-(UIImage *)tintedImageFromImage:(UIImage *)sourceImage colour:(UIColor *)color intensity:(float)intensity {

    if (UIGraphicsBeginImageContextWithOptions != NULL) {

        UIGraphicsBeginImageContextWithOptions(sourceImage.size, NO, 0.0);

    } else {

        UIGraphicsBeginImageContext(sourceImage.size);
    }

    CGContextRef context = UIGraphicsGetCurrentContext();
    CGRect rect = CGRectMake(0, 0, sourceImage.size.width, sourceImage.size.height);

    // draw alpha-mask
    CGContextSetBlendMode(context, kCGBlendModeNormal);
    CGContextDrawImage(context, rect, sourceImage.CGImage);

    // draw tint color, preserving alpha values of original image
    CGContextSetBlendMode(context, kCGBlendModeSourceIn);
    [color setFill];
    CGContextFillRect(context, rect);


    //Set the original greyscale template as the overlay of the new image
    sourceImage = [self verticallyFlipImage:sourceImage];
    [sourceImage drawInRect:CGRectMake(0,0, sourceImage.size.width,sourceImage.size.height) blendMode:kCGBlendModeMultiply alpha:intensity];

    UIImage *colouredImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();

    colouredImage = [self verticallyFlipImage:colouredImage];


    return colouredImage;
}

//Vertically flips an image
-(UIImage *)verticallyFlipImage:(UIImage *)originalImage {

    UIImageView *tempImageView = [[UIImageView alloc] initWithImage:originalImage];

    UIGraphicsBeginImageContext(tempImageView.frame.size);
    CGContextRef context = UIGraphicsGetCurrentContext();
    CGAffineTransform flipVertical = CGAffineTransformMake(1, 0, 0, -1, 0, tempImageView.frame.size.height);

    CGContextConcatCTM(context, flipVertical);

    [tempImageView.layer renderInContext:context];

    UIImage *flippedImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();



    return flippedImage;
}

@end

I've created a test project (in the zip) to illustrate the problem:

Project Files

For future reference, the solution is this one line of code:

tempImageView.image = nil;

Thanks to Matic.

4

1 回答 1

0

看来问题出在方法verticallyFlipImage上。图形上下文似乎保留了您创建的临时图像视图以及您分配的图像。通常可以通过将每个图像作为其自己的调度调用推送通过该过程来解决此问题:重新采样图像 -> 回调 -> 重新采样下一个(或退出)。

在整个重采样结束时,所有数据都被释放并且没有内存泄漏。要快速修复,您只需tempImageView.image = nil;在返回图像之前调用即可。图像视图本身仍然会产生内存膨胀,但它太小而不会产生任何影响。

这对我有用,我希望它对你有帮助。

编辑:添加了调度概念(评论参考)

dispatch_queue_t internalQueue;
- (void)createQueue {
    dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^(void) {
        internalQueue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_SERIAL); //we created a high priority queue
    });
}
- (void)deleteQueue {
    dispatch_release(internalQueue);
}
- (void)imageProcessingDone {
    [self deleteQueue];
    //all done here
}
- (void)processImagesInArray:(NSMutableArray *)imageArray {
    //take out 1 of the objects (last in this case, you can do objectAtIndex:0 if you wish)
    UIImage *img = [[imageArray lastObject] retain]; //note, image retained so the next line does not deallocate it (released at NOTE1)
    [imageArray removeLastObject]; //remove from the array
    dispatch_async(internalQueue, ^(void) { //dispach

        //do all the image processing + saving

        [img release];//NOTE1

        //callback: In this case I push it the main thread. There should be little difference if you simply dispach it again on the internalQueue
        if(imageArray.count > 0) {
            [self performSelectorOnMainThread:@selector(processImagesInArray:) withObject:imageArray waitUntilDone:NO];
        }
        else {
            [self performSelectorOnMainThread:@selector(imageProcessingDone) withObject:nil waitUntilDone:NO];
        }
    });
}
于 2013-06-24T14:02:05.710 回答