Настройка производительности iOS: самый быстрый способ получить пиксельный цвет для больших изображений

Существует несколько вопросов/ответов о том, как получить пиксельный цвет изображения для данной точки. Тем не менее, все эти ответы очень медленные (100-500 мс) для больших изображений (например, даже 1000 х 1300).

В большинстве примеров кода обращается к контексту изображения. Все они занимают время, когда происходит фактическая ничья:

CGContextDrawImage(context, CGRectMake(0.0f, 0.0f, (CGFloat)width, (CGFloat)height), cgImage)

Изучение этого в Инструментах показывает, что ничья выполняется путем копирования данных из исходного изображения:

enter image description here

Я даже попробовал другое средство для получения данных, надеясь, что получение самих байтов фактически окажется намного более эффективным.

NSInteger pointX = trunc(point.x);
NSInteger pointY = trunc(point.y);
CGImageRef cgImage = CGImageCreateWithImageInRect(self.CGImage, 
                           CGRectMake(pointX * self.scale, 
                                      pointY * self.scale, 
                                      1.0f, 
                                      1.0f));

CGDataProviderRef provider = CGImageGetDataProvider(cgImage);
CFDataRef data = CGDataProviderCopyData(provider);

CGImageRelease(cgImage);

UInt8* buffer = (UInt8*)CFDataGetBytePtr(data);

CGFloat red   = (float)buffer[0] / 255.0f;
CGFloat green = (float)buffer[1] / 255.0f;
CGFloat blue  = (float)buffer[2] / 255.0f;
CGFloat alpha = (float)buffer[3] / 255.0f;

CFRelease(data);

UIColor *pixelColor = [UIColor colorWithRed:red green:green blue:blue alpha:alpha];

return pixelColor;

Этот метод занимает время на копии данных:

CFDataRef data = CGDataProviderCopyData(provider);

Похоже, что он тоже читает данные с диска, а не экземпляр CGImage, который я создаю:

enter image description here

Теперь этот метод в некоторых неофициальных тестах работает лучше, но он все еще не так быстр, я хочу, чтобы это было. Кто-нибудь знает о более быстром способе получения базовых данных пикселей?

Ответ 1

Если вы можете нарисовать это изображение на экране через OpenGL ES, вы можете получить чрезвычайно быстрый случайный доступ к базовым пикселям в iOS 5.0 через кэш текстуры, введенный в эту версию. Они позволяют напрямую получать доступ к базовым пиксельным данным BGRA, хранящимся в текстуре OpenGL ES (где будет находиться ваше изображение), и вы могли бы мгновенно выбрать любой пиксель из этой текстуры.

Я использую это для чтения необработанных пиксельных данных даже больших (2048x2048) изображений, а время чтения в худшем случае в диапазоне 10-20 мс, чтобы вытащить все эти пиксели. Опять-таки, случайный доступ к одному пикселю занимает почти нет времени, потому что вы просто читаете из местоположения в массиве байтов.

Конечно, это означает, что вам придется анализировать и загружать ваш конкретный образ в OpenGL ES, который будет включать в себя такое же чтение с диска и взаимодействия с Core Graphics (если вы будете проходить через UIImage), что вы увидите, если вы пытались прочитать данные пикселя из случайного PNG на диске, но это звучит так, как будто вам просто нужно отображать один раз и образец из него несколько раз. Если это так, OpenGL ES и кэширование текстур в iOS 5.0 были бы самым быстрым способом для чтения данных пикселов для чего-то, также отображаемого на экране.

Я инкапсулирую эти процессы в классы GPUImagePicture (загрузка изображений) и GPUImageRawData (быстрый доступ к необработанным данным) в моем открытом исходном коде GPUImage, если вы хотите увидеть, как что-то подобное может работать.

Ответ 2

Мне еще предстоит найти способ получить доступ к выделенным (в буфере кадра) пикселам. Самый быстрый метод, который я измерил:

  • Укажите, что изображение должно быть кэшировано, указав kCGImageSourceShouldCache при его создании.
  • (необязательно) Предварительно очистите изображение, заставив его визуализировать.
  • Нарисуйте изображение в растровом контексте 1x1.

Стоимость этого метода - это кэшированное растровое изображение, которое может иметь срок службы до тех пор, пока он связан с CGImage. В результате код выглядит примерно так:

  • Создать изображение с флагом ShouldCache

    NSDictionary *options = @{ (id)kCGImageSourceShouldCache: @(YES) };
    CGImageSourceRef imageSource = CGImageSourceCreateWithData((__bridge CFDataRef)imageData, NULL);
    CGImageRef cgimage = CGImageSourceCreateImageAtIndex(imageSource, 0, (__bridge CFDictionaryRef)options);
    UIImage *image = [UIImage imageWithCGImage:cgimage];
    CGImageRelease(cgimage);
    
  • Изображение в формате предварительного просмотра

    UIGraphicsBeginImageContext(CGSizeMake(1, 1));
    [image drawAtPoint:CGPointZero];
    UIGraphicsEndImageContext();
    
  • Нарисуйте изображение в растровом контексте 1x1

    unsigned char pixelData[] = { 0, 0, 0, 0 };
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    CGContextRef context = CGBitmapContextCreate(pixelData, 1, 1, 8, 4, colorSpace, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
    CGImageRef cgimage = image.CGImage;
    int imageWidth = CGImageGetWidth(cgimage);
    int imageHeight = CGImageGetHeight(cgimage);
    CGContextDrawImage(context, CGRectMake(-testPoint.x, testPoint.y - imageHeight, imageWidth, imageHeight), cgimage);
    CGColorSpaceRelease(colorSpace);
    CGContextRelease(context);
    

pixelData имеет значения R, G, B и A пикселя в testPoint.

Ответ 3

Контекст CGImage, возможно, почти пуст и не содержит фактических данных пикселя, пока вы не попытаетесь прочитать первый пиксель или нарисуйте его, поэтому попытка ускорить получение пикселей из изображения может не привести вас никуда. Пока ничего не получилось.

Вы пытаетесь читать пиксели из PNG файла? Вы можете попробовать перейти непосредственно после файла и воспроизвести его и самостоятельно декодировать формат PNG. Потребуется некоторое время, чтобы вытащить данные из хранилища.

Ответ 4

- (BOOL)isWallPixel: (UIImage *)image: (int) x :(int) y {

CFDataRef pixelData = CGDataProviderCopyData(CGImageGetDataProvider(image.CGImage));
const UInt8* data = CFDataGetBytePtr(pixelData);

int pixelInfo = ((image.size.width  * y) + x ) * 4; // The image is png

//UInt8 red = data[pixelInfo];         // If you need this info, enable it
//UInt8 green = data[(pixelInfo + 1)]; // If you need this info, enable it
//UInt8 blue = data[pixelInfo + 2];    // If you need this info, enable it
UInt8 alpha = data[pixelInfo + 3];     // I need only this info for my maze game
CFRelease(pixelData);

//UIColor* color = [UIColor colorWithRed:red/255.0f green:green/255.0f blue:blue/255.0f alpha:alpha/255.0f]; // The pixel color info

if (alpha) return YES;
else return NO;
}