Как получить данные о пикселях из UIImage (Cocoa Touch) или CGImage (Core Graphics)?

У меня есть UIImage (Cocoa Touch). Из этого я рад получить CGImage или что-нибудь еще, что вам понравится. Я бы хотел написать эту функцию:

- (int)getRGBAFromImage:(UIImage *)image atX:(int)xx andY:(int)yy {
  // [...]
  // What do I want to read about to help
  // me fill in this bit, here?
  // [...]

  int result = (red << 24) | (green << 16) | (blue << 8) | alpha;
  return result;
}

Спасибо!

Ответ 1

FYI, я объединил ответ Keremk с моим оригинальным контуром, очистил опечатки, обобщил его, чтобы вернуть массив цветов и получил все, что нужно скомпилировать. Вот результат:

+ (NSArray*)getRGBAsFromImage:(UIImage*)image atX:(int)x andY:(int)y count:(int)count
{
    NSMutableArray *result = [NSMutableArray arrayWithCapacity:count];

    // First get the image into your data buffer
    CGImageRef imageRef = [image CGImage];
    NSUInteger width = CGImageGetWidth(imageRef);
    NSUInteger height = CGImageGetHeight(imageRef);
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    unsigned char *rawData = (unsigned char*) calloc(height * width * 4, sizeof(unsigned char));
    NSUInteger bytesPerPixel = 4;
    NSUInteger bytesPerRow = bytesPerPixel * width;
    NSUInteger bitsPerComponent = 8;
    CGContextRef context = CGBitmapContextCreate(rawData, width, height,
                    bitsPerComponent, bytesPerRow, colorSpace,
                    kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
    CGColorSpaceRelease(colorSpace);

    CGContextDrawImage(context, CGRectMake(0, 0, width, height), imageRef);
    CGContextRelease(context);

    // Now your rawData contains the image data in the RGBA8888 pixel format.
    NSUInteger byteIndex = (bytesPerRow * y) + x * bytesPerPixel;
    for (int i = 0 ; i < count ; ++i)
    {
        CGFloat alpha = ((CGFloat) rawData[byteIndex + 3] ) / 255.0f;
        CGFloat red   = ((CGFloat) rawData[byteIndex]     ) / alpha;
        CGFloat green = ((CGFloat) rawData[byteIndex + 1] ) / alpha;
        CGFloat blue  = ((CGFloat) rawData[byteIndex + 2] ) / alpha;
        byteIndex += bytesPerPixel;

        UIColor *acolor = [UIColor colorWithRed:red green:green blue:blue alpha:alpha];
        [result addObject:acolor];
    }

  free(rawData);

  return result;
}

Ответ 2

Один из способов сделать это - нарисовать изображение в контексте растрового изображения, который поддерживается данным буфером для заданного цветового пространства (в данном случае это RGB): (обратите внимание, что это скопирует данные изображения в этот буфер, поэтому вы хотите кэшировать его, а не выполнять эту операцию каждый раз, когда вам нужно получить значения пикселей)

См. ниже в качестве примера:

// First get the image into your data buffer
CGImageRef image = [myUIImage CGImage];
NSUInteger width = CGImageGetWidth(image);
NSUInteger height = CGImageGetHeight(image);
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
unsigned char *rawData = malloc(height * width * 4);
NSUInteger bytesPerPixel = 4;
NSUInteger bytesPerRow = bytesPerPixel * width;
NSUInteger bitsPerComponent = 8;
CGContextRef context = CGBitmapContextCreate(rawData, width, height, bitsPerComponent, bytesPerRow, colorSpace, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
CGColorSpaceRelease(colorSpace);

CGContextDrawImage(context, CGRectMake(0, 0, width, height));
CGContextRelease(context);

// Now your rawData contains the image data in the RGBA8888 pixel format.
int byteIndex = (bytesPerRow * yy) + xx * bytesPerPixel;
red = rawData[byteIndex];
green = rawData[byteIndex + 1];
blue = rawData[byteIndex + 2];
alpha = rawData[byteIndex + 3];

Ответ 3

Apple Технический Q & A QA1509 показывает следующий простой подход:

CFDataRef CopyImagePixels(CGImageRef inImage)
{
    return CGDataProviderCopyData(CGImageGetDataProvider(inImage));
}

Используйте CFDataGetBytePtr, чтобы получить фактические байты (и различные методы CGImageGet*, чтобы понять, как их интерпретировать).

Ответ 4

NSString * path = [[NSBundle mainBundle] pathForResource:@"filename" ofType:@"jpg"];
UIImage * img = [[UIImage alloc]initWithContentsOfFile:path];
CGImageRef image = [img CGImage];
CFDataRef data = CGDataProviderCopyData(CGImageGetDataProvider(image));
const unsigned char * buffer =  CFDataGetBytePtr(data);

Ответ 6

UIImage - это оболочка, байты - CGImage или CIImage

В соответствии с Apple Reference на UIImage объект является неизменным и у вас нет доступа к байтам поддержки. Хотя верно, что вы можете получить доступ к данным CGImage, если вы заполнили UIImage с помощью CGImage (явно или неявно), он вернет NULL, если UIImage будет поддерживаться CIImage и вице- наоборот.

Объекты изображения не обеспечивают прямой доступ к их основному изображению данные. Однако вы можете получить данные изображения в других форматах для используйте в своем приложении. В частности, вы можете использовать cgImage и ciImage свойства для получения версий изображения, которые совместимы с Core Graphics и Core Image, соответственно. Вы также можете использовать UIImagePNGRпредставление (:) и UIImageJPEGRепредставление (: _:) функции для создания объекта NSData, содержащего данные изображения в либо в формате PNG или JPEG.

Общие трюки для решения этой проблемы

Как указано, ваши параметры

  • UIImagePNGRпредставление или JPEG
  • Определите, есть ли у изображения CGImage или CIImage резервные данные и получите его там.

Ни один из них не является особенно хорошим трюком, если вы хотите, чтобы вывод не был ARGB, PNG или JPEG, а данные еще не поддерживаются CIImage.

Моя рекомендация, попробуйте CIImage

При разработке вашего проекта может возникнуть больше смысла избегать UIImage и выбирать что-то еще. UIImage, как обертка изображений Obj-C, часто поддерживается CGImage до такой степени, что мы принимаем это как должное. CIImage имеет тенденцию быть лучшим форматом обертки, поскольку вы можете использовать CIContext, чтобы получить желаемый формат, не зная, как это сделать был создан. В вашем случае получение растрового изображения было бы вопросом вызова

- render: toBitmap: rowBytes: bounds: format: colorSpace:

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

Ответ 7

Основываясь на ответах Оли и Алгала, вот обновленный ответ для Swift 3

public func getRGBAs(fromImage image: UIImage, x: Int, y: Int, count: Int) -> [UIColor] {

var result = [UIColor]()

// First get the image into your data buffer
guard let cgImage = image.cgImage else {
    print("CGContext creation failed")
    return []
}

let width = cgImage.width
let height = cgImage.height
let colorSpace = CGColorSpaceCreateDeviceRGB()
let rawdata = calloc(height*width*4, MemoryLayout<CUnsignedChar>.size)
let bytesPerPixel = 4
let bytesPerRow = bytesPerPixel * width
let bitsPerComponent = 8
let bitmapInfo: UInt32 = CGImageAlphaInfo.premultipliedLast.rawValue | CGBitmapInfo.byteOrder32Big.rawValue

guard let context = CGContext(data: rawdata, width: width, height: height, bitsPerComponent: bitsPerComponent, bytesPerRow: bytesPerRow, space: colorSpace, bitmapInfo: bitmapInfo) else {
    print("CGContext creation failed")
    return result
}

context.draw(cgImage, in: CGRect(x: 0, y: 0, width: width, height: height))

// Now your rawData contains the image data in the RGBA8888 pixel format.
var byteIndex = bytesPerRow * y + bytesPerPixel * x

for _ in 0..<count {
    let alpha = CGFloat(rawdata!.load(fromByteOffset: byteIndex + 3, as: UInt8.self)) / 255.0
    let red = CGFloat(rawdata!.load(fromByteOffset: byteIndex, as: UInt8.self)) / alpha
    let green = CGFloat(rawdata!.load(fromByteOffset: byteIndex + 1, as: UInt8.self)) / alpha
    let blue = CGFloat(rawdata!.load(fromByteOffset: byteIndex + 2, as: UInt8.self)) / alpha
    byteIndex += bytesPerPixel

    let aColor = UIColor(red: red, green: green, blue: blue, alpha: alpha)
    result.append(aColor)
}

free(rawdata)

return result
}

Ответ 8

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

UIImage *image1 = ...; // The image from where you want a pixel data
int pixelX = ...; // The X coordinate of the pixel you want to retrieve
int pixelY = ...; // The Y coordinate of the pixel you want to retrieve

uint32_t pixel1; // Where the pixel data is to be stored
CGContextRef context1 = CGBitmapContextCreate(&pixel1, 1, 1, 8, 4, CGColorSpaceCreateDeviceRGB(), kCGImageAlphaNoneSkipFirst);
CGContextDrawImage(context1, CGRectMake(-pixelX, -pixelY, CGImageGetWidth(image1.CGImage), CGImageGetHeight(image1.CGImage)), image1.CGImage);
CGContextRelease(context1);

В результате этих строк у вас будет пиксель в формате AARRGGBB с альфами, всегда установленными в FF в 4-байтовое беззнаковое целое число pixel1.