Работа с данными изображения

Допустим, вам захотелось странного – получить значения цветов в каждом пикселе изображения. Например, у вас есть некая библиотека обработки изображений, написанная на C или C++, и работает она с массивами байт.

В iOS мы, как правило, манипулируем объектами UIImage. Теперь нам потребуется спуститься на уровень ниже – Core Graphics.

Core Graphics – это C-фреймворк для работы с изображениями. C-функции и типы данных с префиксом CG – это и есть Core Graphics. Так что сейчас нам придется комбинировать Objective-C и C код, но нам поможет тот факт, некоторые классы Objective-C имеют эквиваленты в C, и они преобразуются друг в друга без особых проблем.

На самом деле, здесь есть две задачи – получить массив с пикелами из UIImage и создать UIImage из массива. Еще нужно заметить, что помимо самого массива пикселов нам понадобится следующая информация об изображении:

  • Размер изображения (ширина x высота) в пикселах;
  • Количество компонентов цвета пиксела;
  • Количество бит, которым кодируется один компонент цвета;
  • Метаданные изображения (например, есть ли альфа-канал, как он закодирован и т.п.).

Имея объект UIImage, мы можем легко получить доступ к “низкоуровневому” объекту CGImage с помощью свойства CGImage.

UIImage *image; // got from somewhere

NSUInteger width = CGImageGetWidth(image.CGImage);
NSUInteger height = CGImageGetHeight(image.CGImage);
NSUInteger bitsPerComponent = CGImageGetBitsPerComponent(image.CGImage);
NSUInteger components = CGImageGetBitsPerPixel(image.CGImage) / CGImageGetBitsPerComponent(image.CGImage);
CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(image.CGImage);

CGDataProviderRef imageDataProviderRef = CGImageGetDataProvider(image.CGImage);
NSData *imageData = (__bridge_transfer NSData*) CGDataProviderCopyData(imageDataProviderRef);

Здесь мы сначала получаем характеристики изображения (размеры в пикселах, количество компонент кодирования цвета). Затем, используя CGDataProviderRef (Ref – от слова reference) мы получаем данные.

Нужно отметить, что в отличие от координат в iOS, которые являются CGFloat (дробные числа), размеры изображения – целые числа, что вполне логично.

Теперь в imageData у нас хранится массив данных изображения. Из объекта NSData мы легко можем получить C-массив байт, который уже обрабатывать. Стоит учесть, что размер такого массива будет немаленьким; мы имеем дело с несжатыми данными (например, 8-мегапиксельное изображение будет занимать 32 мегабайта оперативной памяти).

NSMutableString *desc = [NSMutableString string];
unsigned char *rawImageData = (unsigned char *)imageData.bytes;
size_t bytesPerPixel = components * bitsPerComponent / 8;
size_t bytesPerRow = width * bytesPerPixel;
for (size_t i = 0; i < height; i++)
{
    for (size_t j = 0; j < width; j++)
    {
        [desc appendString:@"("];
        for (size_t k = 0; k < bytesPerPixel; k++)
        {
            if (k)
            {
                 [desc appendString:@","];
            }
            [desc appendFormat:@"%.2X", rawImageData[i * bytesPerRow + j * bytesPerPixel + k]];
        }
        [desc appendString:@") "];
    }
    [desc appendString:@"\n"];
}
NSLog(@"Image data:\n%@", desc);

Этот код не является эффективным. Разумеется не стоит делать столько умножений, чтобы просто последовательно пробежаться по массиву. Более того, вряд ли потребуется так где-то отображать картинку. Написан он для наглядности – изображение хранится в виде последовательности рядов точек (сверху вниз, слева направо), каждая точка кодируется указанным количеством байт.

Пример вывода может быть таким:

Image data:
(00,00,00,FF) (00,00,00,FF) (FF,FF,FF,FF) (FF,FF,FF,FF) (FF,FF,FF,FF) (FF,FF,FF,FF) (FF,FF,FF,FF) (FF,FF,FF,FF) (00,00,00,FF) (00,00,00,FF) 
(00,00,00,FF) (00,00,00,FF) (FF,FF,FF,FF) (FF,FF,FF,FF) (FF,FF,FF,FF) (FF,FF,FF,FF) (FF,FF,FF,FF) (FF,FF,FF,FF) (00,00,00,FF) (00,00,00,FF) 
(FF,FF,FF,FF) (FF,FF,FF,FF) (00,00,00,FF) (FF,FF,FF,FF) (FF,FF,FF,FF) (FF,FF,FF,FF) (FF,FF,FF,FF) (00,00,00,FF) (FF,FF,FF,FF) (FF,FF,FF,FF) 
(FF,FF,FF,FF) (FF,FF,FF,FF) (FF,FF,FF,FF) (00,00,00,FF) (FF,FF,FF,FF) (FF,FF,FF,FF) (00,00,00,FF) (FF,FF,FF,FF) (FF,FF,FF,FF) (FF,FF,FF,FF) 
(FF,FF,FF,FF) (FF,FF,FF,FF) (FF,FF,FF,FF) (FF,FF,FF,FF) (00,00,00,FF) (00,00,00,FF) (FF,FF,FF,FF) (FF,FF,FF,FF) (FF,FF,FF,FF) (FF,FF,FF,FF) 
(FF,FF,FF,FF) (FF,FF,FF,FF) (FF,FF,FF,FF) (FF,FF,FF,FF) (00,00,00,FF) (00,00,00,FF) (FF,FF,FF,FF) (FF,FF,FF,FF) (FF,FF,FF,FF) (FF,FF,FF,FF) 
(FF,FF,FF,FF) (FF,FF,FF,FF) (FF,FF,FF,FF) (00,00,00,FF) (FF,FF,FF,FF) (FF,FF,FF,FF) (00,00,00,FF) (FF,FF,FF,FF) (FF,FF,FF,FF) (FF,FF,FF,FF) 
(FF,FF,FF,FF) (FF,FF,FF,FF) (00,00,00,FF) (FF,FF,FF,FF) (FF,FF,FF,FF) (FF,FF,FF,FF) (FF,FF,FF,FF) (00,00,00,FF) (FF,FF,FF,FF) (FF,FF,FF,FF) 
(00,00,00,FF) (00,00,00,FF) (FF,FF,FF,FF) (FF,FF,FF,FF) (FF,FF,FF,FF) (FF,FF,FF,FF) (FF,FF,FF,FF) (FF,FF,FF,FF) (00,00,00,FF) (00,00,00,FF) 
(00,00,00,FF) (00,00,00,FF) (FF,FF,FF,FF) (FF,FF,FF,FF) (FF,FF,FF,FF) (FF,FF,FF,FF) (FF,FF,FF,FF) (FF,FF,FF,FF) (00,00,00,FF) (00,00,00,FF) 

Теперь давайте посмотрим, как из этих данных собрать обратно UIImage.

CGDataProviderRef imageDataProviderRef = CGDataProviderCreateWithData(NULL,
                                                                      imageData.bytes,
                                                                      imageData.length,
                                                                      NULL);
CGColorSpaceRef colorSpaceRef = CGColorSpaceCreateDeviceRGB();
        
CGImageRef imageRef = CGImageCreate(width,
                                    height,
                                    bitsPerComponent,
                                    bitsPerComponent * components,
                                    width * components * bitsPerComponent / 8,
                                    colorSpaceRef,
                                    bitmapInfo,
                                    imageDataProviderRef,
                                    NULL,
                                    NO,
                                    kCGRenderingIntentDefault);
        
UIImage *image = [UIImage imageWithCGImage:imageRef];
       
CGImageRelease(imageRef);
CGColorSpaceRelease(colorSpaceRef);
CGDataProviderRelease(imageDataProviderRef);

В данном примере мы снова используем CGDataProviderRef. В этот раз мы используем массив байт, вместе с данными об изображении, чтобы создать объект CGImageRef, с помощью которого мы инициализируем UIImage.

В Core Graphics (и, вообще, в Core Foundation) ARC пока не действует. Так что мы должны вручную освобождать созданные объекты с помощью функций ...Release (освобождать нужно объекты, которые создавались с помощью функций, содержащих в названии Copy или Create).

Пример исходного кода выложен на GitHub.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s