【问题标题】:Generate and store many images at first launch iOS在首次启动 iOS 时生成并存储许多图像
【发布时间】:2013-06-20 14:59:53
【问题描述】:

第一次运行游戏时,我需要生成 320 张图片并将其保存为 PNG。然后将加载这些图像,而不是再次生成这些图像。流程如下:

  • 加载图像模板(带 alpha 的黑白)
  • 用指定颜色覆盖非透明像素
  • 将不透明度为 0.3 的模板放在模板顶部,将其合并为一张最终图像
  • 返回 UIImage
  • 将 UIImage 转换为 NSData 为 PNG 存储在 Cache 目录中

这是使用 UIGraphicsBeginImageContextWithOptions 完成的。此过程需要在后台线程上为 10 种颜色的 32 个图像模板完成。目的是这些将用作该游戏中的头像/个人资料图像,并在某些屏幕上酌情缩小。但它们不能每次都生成,因为这会导致太多延迟。

每张图片的尺寸为 400x400。存储时,它们的大小约为 20/25 kB。当我尝试使用当前的生成和存储方式时,我收到了内存警告,并且我看到(使用 Instruments)活动的 CGImage 和 UIImage 对象的数量不断快速增加。这似乎它们被保留了,但我没有对它们的任何引用。

这是我的另一个问题,详细说明了我正在使用的代码:UIGraphicsBeginImageContext created image

创建这么多图像并将其存储到辅助存储的最佳方法是什么?提前致谢。

编辑:

这是我目前用于创建和保存图像的全部代码:

//==========================================================
// 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

我创建了一个测试项目(在 zip 中)来说明问题:

Project Files

为了以后参考,解决方法是这行代码:

tempImageView.image = nil;

感谢 Matic。

【问题讨论】:

  • 您没有发布任何代码,但考虑到您编写的内容,这可能是自动释放池的问题:如果您使用“for”循环,您应该在循环内创建内部自动释放池并将其排空循环以确保释放里面的那些对象。
  • 我尝试将 for 循环的内部包裹在 @autorelease {} 中,但没有帮助。有什么我需要添加以排出它吗?我以为是自动的。谢谢
  • 我通常分配自动释放池,然后将其排空,但我认为这应该做同样的事情。它是自动的,但它不会简单地阻止你的代码并释放推送到自动释放池的对象,除非你指定所以。无论如何,似乎有些东西正在保留图像。如果可能(并且没有其他工作),请尝试将 UIImage 子类化并覆盖保留和释放方法以查看哪些对象保留了它们。你也在用ARC吗?能不能看到一些相关的代码?
  • ..不是那些着色和翻转图像的方法(那些看起来不错),带有“for”循环和自动释放池的代码可能会有所帮助。
  • 我会在几分钟后用循环代码更新问题。

标签: ios objective-c multithreading image colors


【解决方案1】:

看来问题出在方法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];
        }
    });
}

【讨论】:

  • 我正在尝试通过按照您的建议在每个自己的调度调用中创建每个图像来完全优化这一点。奇怪的是,完成后我还剩下 39 个活着的 CGImages 和 37 个 UIImages。我怎样才能做到这一点?绝对没有应该加载的内容。
  • 经过一番折腾后,我会选择“快速修复”。在自己的调度调用中制作每个图像根本没有任何区别。我会接受这个作为答案。
  • 我想你误解了.. 你应该处理同一个队列中的所有图像,但你应该处理第一个图像,而不是调用 for 循环,完成后获取回调,调度另一个图像,获取完成后回调,派发另一个图像...直到所有图像都重新采样。如果您只是将每张图像放在自己的队列中,它会尝试同时处理所有图像,这会导致极大的内存消耗。
  • 我创建了一个递归方法,它完全按照您的描述执行操作,它执行一个图像,然后在回调中使用新图像名称再次调用自身。所以我认为这应该一次只做一个。但最后还有 39 个 CGImage 和 37 个 UIImage。
  • 奇怪,这应该是防弹的。虽然我希望你没有做直接递归,因为这与“for循环”相同或更糟,你需要调用dispatch async for每张要处理的图像。
猜你喜欢
  • 1970-01-01
  • 2014-07-09
  • 2013-05-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-07-15
  • 2015-01-15
  • 2020-08-20
相关资源
最近更新 更多