UIGraphicsBeginImageContext против CGBitmapContextCreate

Я пытаюсь изменить цвет изображения в фоновом потоке.
В документации Apple говорится, что UIGraphicsBeginImageContext можно вызывать только из основного потока, и я пытаюсь использовать CGBitmapContextCreate:

context = CGBitmapContextCreate(bitmapData, пиксели в ширину, пиксели в высоту, 8, // бит на компонент

                             bitmapBytesPerRow,
                             colorSpace,
                             kCGImageAlphaPremultipliedFirst);

У меня есть две версии «changeColor», первая с использованием UIGraphisBeginImageContext, вторая с использованием CGBitmapContextCreate.

Первый корректно меняет цвет, а второй нет.
Почему?

- (UIImage*) changeColor: (UIColor*) aColor
{
    if(aColor == nil)
        return self;

    UIGraphicsBeginImageContext(self.size);

    CGRect bounds;
    bounds.origin = CGPointMake(0,0);
    bounds.size = self.size;
    [aColor set];

    CGContextRef context = UIGraphicsGetCurrentContext();
    CGContextTranslateCTM(context, 0, self.size.height);
    CGContextScaleCTM(context, 1.0, -1.0);

    CGContextClipToMask(context, bounds, [self CGImage]);
    CGContextFillRect(context, bounds);

    UIImage *img = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return img;
}

- (UIImage*) changeColor: (UIColor*) aColor
{
    if(aColor == nil)
        return self;

    CGContextRef context = CreateARGBBitmapContext(self.size);

    CGRect bounds;
    bounds.origin = CGPointMake(0,0);
    bounds.size = self.size;

    CGColorRef colorRef = aColor.CGColor;
    const CGFloat *components = CGColorGetComponents(colorRef);
    float red = components[0];
    float green = components[1];
    float blue = components[2];

    CGContextSetRGBFillColor(context, red, green, blue, 1);


    CGContextClipToMask(context, bounds, [self CGImage]);
    CGContextFillRect(context, bounds);

    CGImageRef imageRef = CGBitmapContextCreateImage(context);
    UIImage* img = [UIImage imageWithCGImage: imageRef];
    unsigned char* data = (unsigned char*)CGBitmapContextGetData (context);  
    CGContextRelease(context);
    free(data);

    return img;
}

CGContextRef CreateARGBBitmapContext(CGSize size)
{
    CGContextRef    context = NULL;
    CGColorSpaceRef colorSpace;
    void *          bitmapData;
    int             bitmapByteCount;
    int             bitmapBytesPerRow;

    // Get image width, height. We'll use the entire image.                                                                                                                                                                                 
    size_t pixelsWide = size.width;
    size_t pixelsHigh = size.height;

    // Declare the number of bytes per row. Each pixel in the bitmap in this                                                                                                                                                                
    // example is represented by 4 bytes; 8 bits each of red, green, blue, and                                                                                                                                                              
    // alpha.                                                                                                                                                                                                                               
    bitmapBytesPerRow   = (pixelsWide * 4);
    bitmapByteCount     = (bitmapBytesPerRow * pixelsHigh);

    // Use the generic RGB color space.                                                                                                                                                                                                     
    colorSpace = CGColorSpaceCreateDeviceRGB();

    if (colorSpace == NULL)
    {
        fprintf(stderr, "Error allocating color space\n");
        return NULL;
    }

    // Allocate memory for image data. This is the destination in memory                                                                                                                                                                    
    // where any drawing to the bitmap context will be rendered.                                                                                                                                                                            
    bitmapData = malloc( bitmapByteCount );
    if (bitmapData == NULL)
    {
        fprintf (stderr, "Memory not allocated!");
        CGColorSpaceRelease( colorSpace );
        return NULL;
    }
    // Create the bitmap context. We want pre-multiplied ARGB, 8-bits                                                                                                                                                                       
    // per component. Regardless of what the source image format is                                                                                                                                                                         
    // (CMYK, Grayscale, and so on) it will be converted over to the format                                                                                                                                                                 
    // specified here by CGBitmapContextCreate.                                                                                                                                                                                             
    context = CGBitmapContextCreate (bitmapData,
                                     pixelsWide,
                                     pixelsHigh,
                                     8,      // bits per component                                                                                                                                                                          
                                     bitmapBytesPerRow,
                                     colorSpace,
                                     kCGImageAlphaPremultipliedFirst);
    if (context == NULL)
    {
        free (bitmapData);
        fprintf (stderr, "Context not created!");
    }

    // Make sure and release colorspace before returning                                                                                                                                                                                    
    CGColorSpaceRelease( colorSpace );

    return context;

}

person eugene    schedule 13.01.2011    source источник
comment
На самом деле, в iOS 4.0 функции рисования UIKit теперь являются потокобезопасными: ">cocoabuilder.com/archive/cocoa/ . Похоже, это включает UIGraphicsBeginImageContext(). Вам может не понадобиться чистая реализация Core Graphics в 4.0+.   -  person Brad Larson    schedule 13.01.2011
comment
цитата авторитетна? stackoverflow.com/questions/4451855/ там Я думаю, это дебаты по этому поводу?, и мой тестовый код умер с UIGraphicsBeginImageContext() в отдельном потоке. хотя я не могу быть на 100% уверен, что это настоящая причина.   -  person eugene    schedule 13.01.2011
comment
это другой вопрос, когда вы говорите iOS 4.0, применимо ли это, когда я ориентируюсь на iOS ‹ 4.0, но компилирую с iOS ›= 4.0? или код должен работать только на машине с iOS ›=4.0?   -  person eugene    schedule 13.01.2011
comment
Дэвид Дункан — инженер Apple, поэтому я бы сказал, что его заявление в сочетании с примечаниями к выпуску является авторитетным. Это потокобезопасно только при запуске на устройствах iOS 4.0+, потому что сборка с SDK 4.x по-прежнему использует локальную реализацию ОС, если ориентируется на более старые версии. Если вам нужна поддержка более старых ОС, вам все равно придется идти по пути чистой Core Graphics.   -  person Brad Larson    schedule 13.01.2011


Ответы (1)


Ваш второй метод выполняет работу, которую первый никогда не делал. Вот корректировка второго метода для более точного соответствия первому:

- (UIImage*) changeColor: (UIColor*) aColor
{
    if(aColor == nil)
        return self;

    CGContextRef context = CreateARGBBitmapContext(self.size);

    CGRect bounds;
    bounds.origin = CGPointMake(0,0);
    bounds.size = self.size;

    CGContextSetFillColorWithColor(aColor.CGColor);

    CGContextClipToMask(context, bounds, [self CGImage]);
    CGContextFillRect(context, bounds);

    CGImageRef imageRef = CGBitmapContextCreateImage(context);
    UIImage* img = [UIImage imageWithCGImage: imageRef];
    CGContextRelease(context);

    return img;
}

Я сделал два изменения: я преобразовал это, чтобы использовать CGContextSetFillColorWithColor(), и я удалил опасные и неправильные free() резервных данных контекста растрового изображения. Если этот фрагмент кода ведет себя не так, как первый, вам придется посмотреть на свою реализацию CreateARGBBitmapContext(), чтобы убедиться, что она правильная.

Конечно, как упомянул Брэд Ларсон в комментариях, если вы ориентируетесь на iOS 4.0 и выше, графические методы UIKit (согласно примечаниям к выпуску) потокобезопасны, и вы сможете использовать первый метод просто отлично.

person Lily Ballard    schedule 13.01.2011
comment
ах, спасибо! .. я вижу, что объем памяти увеличивается, когда у меня нет «бесплатного», может быть, я делаю что-то неправильно в CreateARGBBitmapContext? Я обновил вопрос с функцией, не могли бы вы взглянуть на нее, пожалуйста? - person eugene; 14.01.2011
comment
А, я вижу, что происходит. Вы выделяете свои собственные данные, а затем отбрасываете ссылку на них. Так что эти данные действительно нужно опубликовать позже. Просто довольно неприятно вытаскивать данные из контекста и вызывать для этого free(). Если вам нужна альтернатива, вместо malloc() вы можете создать NSMutableData нужного размера и взять -mutableBytes. Это даст вам тот же буфер, но автоматически освобожденный. Конечно, если вы сделаете это, контекст должен быть освобожден до того, как появится пул автоматического освобождения (но вы все равно делаете это здесь). - person Lily Ballard; 14.01.2011
comment
еще одна вещь, я должен выпустить imageRef с CGImageRelease? - person eugene; 14.01.2011
comment
ненавижу документацию Apple, четко не говорит, нужно ли мне ее выпускать или нет :( - person eugene; 14.01.2011
comment
О да, вы явно должны. CGBitmapContextCreateImage() дает вам собственную ссылку. +[UIImage imageWithCGImage:] сохранит CGImageRef на время существования объекта UIImage. Но у вас все еще есть собственная ссылка, которую вам нужно освободить с помощью CGImageRelease(). - person Lily Ballard; 14.01.2011
comment
Я реализовывал CreateRGBBitmapContext, и из-за этого malloc у меня была сумасшедшая утечка памяти. Хороший колл с NSMutableData. Если кому-то это нужно, вот две строки для замены вызова malloc: NSMutableData* mutableData = [NSMutableData dataWithLength: bitmapByteCount]; bitmapData = [mutableData mutableBytes]; - person Gujamin; 06.03.2013