Как получить значения RGB для пикселя изображения на iPhone
-
02-07-2019 - |
Вопрос
Я пишу приложение для iPhone, и мне нужно, по сути, реализовать что-то эквивалентное инструменту «Пипетка» в фотошопе, где вы можете коснуться точки на изображении и захватить значения RGB для соответствующего пикселя, чтобы определить и сопоставить его цвет.Получение UIImage — это простая часть, но есть ли способ преобразовать данные UIImage в растровое представление, в котором я мог бы извлечь эту информацию для данного пикселя?Пример рабочего кода был бы очень признателен, и обратите внимание, что меня не интересует значение альфа.
Решение
Немного подробнее...
Ранее этим вечером я опубликовал сообщение с обобщением и небольшим дополнением к тому, что было сказано на этой странице — это можно найти внизу этого сообщения.Однако на данный момент я редактирую сообщение, чтобы опубликовать то, что я предлагаю (по крайней мере, для моих требований, которые включают изменение данных пикселей), лучший метод, поскольку он предоставляет записываемые данные (тогда как, насколько я понимаю, метод, предоставленный предыдущими сообщениями, а внизу этого сообщения содержится ссылка на данные, доступная только для чтения).
Метод 1:Записываемая информация о пикселях
Я определил константы
#define RGBA 4 #define RGBA_8_BIT 8
В моем подклассе UIImage я объявил переменные экземпляра:
size_t bytesPerRow; size_t byteCount; size_t pixelCount; CGContextRef context; CGColorSpaceRef colorSpace; UInt8 *pixelByteData; // A pointer to an array of RGBA bytes in memory RPVW_RGBAPixel *pixelData;
Структура пикселя (в этой версии с альфа-каналом)
typedef struct RGBAPixel { byte red; byte green; byte blue; byte alpha; } RGBAPixel;
Функция растрового изображения (возвращает предварительно рассчитанное значение RGBA;разделите RGB на A, чтобы получить неизмененный RGB):
-(RGBAPixel*) bitmap { NSLog( @"Returning bitmap representation of UIImage." ); // 8 bits each of red, green, blue, and alpha. [self setBytesPerRow:self.size.width * RGBA]; [self setByteCount:bytesPerRow * self.size.height]; [self setPixelCount:self.size.width * self.size.height]; // Create RGB color space [self setColorSpace:CGColorSpaceCreateDeviceRGB()]; if (!colorSpace) { NSLog(@"Error allocating color space."); return nil; } [self setPixelData:malloc(byteCount)]; if (!pixelData) { NSLog(@"Error allocating bitmap memory. Releasing color space."); CGColorSpaceRelease(colorSpace); return nil; } // Create the bitmap context. // Pre-multiplied RGBA, 8-bits per component. // The source image format will be converted to the format specified here by CGBitmapContextCreate. [self setContext:CGBitmapContextCreate( (void*)pixelData, self.size.width, self.size.height, RGBA_8_BIT, bytesPerRow, colorSpace, kCGImageAlphaPremultipliedLast )]; // Make sure we have our context if (!context) { free(pixelData); NSLog(@"Context not created!"); } // Draw the image to the bitmap context. // The memory allocated for the context for rendering will then contain the raw image pixelData in the specified color space. CGRect rect = { { 0 , 0 }, { self.size.width, self.size.height } }; CGContextDrawImage( context, rect, self.CGImage ); // Now we can get a pointer to the image pixelData associated with the bitmap context. pixelData = (RGBAPixel*) CGBitmapContextGetData(context); return pixelData; }
Данные только для чтения (предыдущая информация) – метод 2:
Шаг 1.Я объявил тип для байта:
typedef unsigned char byte;
Шаг 2.Я объявил структуру, соответствующую пикселю:
typedef struct RGBPixel{
byte red;
byte green;
byte blue;
}
RGBPixel;
Шаг 3.Я создал подкласс UIImageView и объявил (с соответствующими синтезированными свойствами):
// Reference to Quartz CGImage for receiver (self)
CFDataRef bitmapData;
// Buffer holding raw pixel data copied from Quartz CGImage held in receiver (self)
UInt8* pixelByteData;
// A pointer to the first pixel element in an array
RGBPixel* pixelData;
Шаг 4.Код подкласса я поместил в метод с именем bitmap (чтобы вернуть данные пикселей растрового изображения):
//Get the bitmap data from the receiver's CGImage (see UIImage docs)
[self setBitmapData: CGDataProviderCopyData(CGImageGetDataProvider([self CGImage]))];
//Create a buffer to store bitmap data (unitialized memory as long as the data)
[self setPixelBitData:malloc(CFDataGetLength(bitmapData))];
//Copy image data into allocated buffer
CFDataGetBytes(bitmapData,CFRangeMake(0,CFDataGetLength(bitmapData)),pixelByteData);
//Cast a pointer to the first element of pixelByteData
//Essentially what we're doing is making a second pointer that divides the byteData's units differently - instead of dividing each unit as 1 byte we will divide each unit as 3 bytes (1 pixel).
pixelData = (RGBPixel*) pixelByteData;
//Now you can access pixels by index: pixelData[ index ]
NSLog(@"Pixel data one red (%i), green (%i), blue (%i).", pixelData[0].red, pixelData[0].green, pixelData[0].blue);
//You can determine the desired index by multiplying row * column.
return pixelData;
Шаг 5.Я создал метод доступа:
-(RGBPixel*)pixelDataForRow:(int)row column:(int)column{
//Return a pointer to the pixel data
return &pixelData[row * column];
}
Другие советы
Вот мое решение для выборки цвета UIImage.
Этот подход отображает запрошенный пиксель в буфере RGBA размером 1 пиксель и возвращает полученные значения цвета в виде объекта UIColor.Это намного быстрее, чем большинство других подходов, которые я видел, и использует очень мало памяти.
Это должно работать очень хорошо для чего-то вроде палитры цветов, где вам обычно нужно значение только одного конкретного пикселя в любой момент времени.
Uiimage+Picker.h
#import <UIKit/UIKit.h>
@interface UIImage (Picker)
- (UIColor *)colorAtPosition:(CGPoint)position;
@end
Uiimage+Picker.m
#import "UIImage+Picker.h"
@implementation UIImage (Picker)
- (UIColor *)colorAtPosition:(CGPoint)position {
CGRect sourceRect = CGRectMake(position.x, position.y, 1.f, 1.f);
CGImageRef imageRef = CGImageCreateWithImageInRect(self.CGImage, sourceRect);
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
unsigned char *buffer = malloc(4);
CGBitmapInfo bitmapInfo = kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big;
CGContextRef context = CGBitmapContextCreate(buffer, 1, 1, 8, 4, colorSpace, bitmapInfo);
CGColorSpaceRelease(colorSpace);
CGContextDrawImage(context, CGRectMake(0.f, 0.f, 1.f, 1.f), imageRef);
CGImageRelease(imageRef);
CGContextRelease(context);
CGFloat r = buffer[0] / 255.f;
CGFloat g = buffer[1] / 255.f;
CGFloat b = buffer[2] / 255.f;
CGFloat a = buffer[3] / 255.f;
free(buffer);
return [UIColor colorWithRed:r green:g blue:b alpha:a];
}
@end
Вы не можете напрямую получить доступ к растровым данным UIImage.
Вам нужно получить представление CGImage для UIImage.Затем получите поставщика данных CGImage, из которого будет представлено растровое изображение CFData.По завершении обязательно освободите CFData.
CGImageRef cgImage = [image CGImage];
CGDataProviderRef provider = CGImageGetDataProvider(cgImage);
CFDataRef bitmapData = CGDataProviderCopyData(provider);
Вероятно, вам захочется просмотреть информацию о растровом изображении CGImage, чтобы узнать порядок пикселей, размеры изображения и т. д.
Ответ Лайоша сработал для меня.Чтобы получить данные пикселей в виде массива байтов, я сделал следующее:
UInt8* data = CFDataGetBytePtr(bitmapData);
Больше информации:CFDataRef документация.
Также не забудьте включить CoreGraphics.framework
Всем спасибо!Объединив несколько ответов, я получаю:
- (UIColor*)colorFromImage:(UIImage*)image sampledAtPoint:(CGPoint)p {
CGImageRef cgImage = [image CGImage];
CGDataProviderRef provider = CGImageGetDataProvider(cgImage);
CFDataRef bitmapData = CGDataProviderCopyData(provider);
const UInt8* data = CFDataGetBytePtr(bitmapData);
size_t bytesPerRow = CGImageGetBytesPerRow(cgImage);
size_t width = CGImageGetWidth(cgImage);
size_t height = CGImageGetHeight(cgImage);
int col = p.x*(width-1);
int row = p.y*(height-1);
const UInt8* pixel = data + row*bytesPerRow+col*4;
UIColor* returnColor = [UIColor colorWithRed:pixel[0]/255. green:pixel[1]/255. blue:pixel[2]/255. alpha:1.0];
CFRelease(bitmapData);
return returnColor;
}
Это просто занимает диапазон точек 0,0–1,0 как для x, так и для y.Пример:
UIColor* sampledColor = [self colorFromImage:image
sampledAtPoint:CGPointMake(p.x/imageView.frame.size.width,
p.y/imageView.frame.size.height)];
Это отлично работает для меня.Я делаю пару предположений, таких как бит на пиксель и цветовое пространство RGBA, но в большинстве случаев это должно работать.
Еще одно замечание: у меня он работает как на симуляторе, так и на устройстве. Раньше у меня были проблемы с этим из-за оптимизации PNG, которая происходила при его запуске на устройстве.
Чтобы сделать что-то подобное в своем приложении, я создал небольшой внеэкранный CGImageContext, а затем отрисовал в него UIImage.Это позволило мне быстро извлечь несколько пикселей одновременно.Это означает, что вы можете настроить целевое растровое изображение в формате, который вам будет легко анализировать, и позволить CoreGraphics выполнить тяжелую работу по преобразованию между цветовыми моделями или форматами растровых изображений.
Я не знаю, как правильно индексировать данные изображения на основе заданной координаты X,Y.Кто-нибудь знает?
PixelPosition = (x+(y*((imagewidth)*BytesPerPixel)));
// Насколько мне известно, шаг не является проблемой для этого устройства, и его можно оставить равным нулю...// (или вытащено из математики).
Использовать ANImageBitmapRep который обеспечивает доступ на уровне пикселей (чтение/запись).