Як отримати дані пікселів з UIImage (Cocoa Touch) або CGImage (Core Graphics)?


200

У мене є UIImage (какао-дотик). З цього приводу я отримаю 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;
}

Відповіді:


249

FYI, я поєднав відповідь Керемка з моїм оригінальним контуром, очистив друкарські помилки, узагальнив його, щоб повернути масив кольорів і отримав все, що потрібно скласти. Ось результат:

+ (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;
}

4
Не забувайте розмножувати кольори. Крім того, ці множення на 1 нічого не роблять.
Пітер Хосей

2
Навіть якщо це правильно. Коли кількість рахунків велика (наприклад, довжина рядка зображення або розмір всього зображення!), Я не вважаю, що ефективність цього методу буде хорошою, оскільки занадто багато об'єктів UIColor дуже важке. У реальному житті, коли мені потрібен піксель, тоді UIColor все в порядку, (NSArray UIColors - це занадто багато для мене). І коли вам потрібна купа кольорів, я не буду використовувати UIColors, оскільки я, ймовірно, буду використовувати OpenGL тощо. На мою думку, цей метод не використовує реального життя, але дуже хороший для навчальних цілей.
nacho4d

4
Занадто громіздкий великий простір лише для читання одного пікселя.
ragnarius

46
ВИКОРИСТОВУЙТЕ CALLOC ВМЕСТО МАЛОКА !!!! Я використовував цей код, щоб перевірити, чи певні пікселі були прозорими, і я отримував неправдиву інформацію, де пікселі мали бути прозорими, оскільки пам'ять спочатку не була очищена. Використовуючи calloc (ширина * висота, 4) замість malloc зробив трюк. Решта коду чудово, ДЯКУЄМО!
DonnaLea

5
Це чудово. Дякую. Примітка щодо використання: x і y - координати всередині зображення, щоб почати отримувати RGBA з, а 'count' - це кількість пікселів з цієї точки, щоб отримати, переходячи ліворуч праворуч, а потім рядок за рядком. Приклад використання: Щоб отримати RGBA для всього зображення: getRGBAsFromImage:image atX:0 andY:0 count:image.size.width*image.size.height Отримати RGBA для останнього рядка зображення: getRGBAsFromImage:image atX:0 andY:image.size.height-1 count:image.size.width
Harris

49

Один із способів зробити це - намалювати зображення до контексту растрових зображень, який підтримується заданим буфером для заданого кольорового простору (у цьому випадку це 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];

Я щось подібне зробив у своєму додатку. Щоб витягти довільний піксель, ви просто намалюєте 1x1 бітову карту з відомим форматом растрових зображень, коректуючи походження CGBitmapContext відповідним чином.
Марк Бессі

5
Це просочує пам'ять. Випуск rawData: безкоштовно (rawData);
Адам Уайт

5
CGContextDrawImageвимагає трьох аргументів і вище наведено лише два ... Я думаю, що це має бутиCGContextDrawImage(context, CGRectMake(0, 0, width, height), image)
user1135469

25

Apple, технічний Q & QA1509 показує наступний простий підхід:

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

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


10
Це не чудовий підхід, оскільки формат пікселів, як правило, сильно змінюється в залежності від зображення. Є декілька речей, які можуть змінитися, включаючи 1. орієнтацію зображення 2. формат альфа-компонента та 3. порядок байт RGB. Я особисто витратив деякий час, намагаючись розшифрувати документи Apple, як це зробити, але я не впевнений, що цього варто. Якщо ви хочете, щоб це було швидко, просто вирішіть керемк.
Тайлер

1
Цей метод завжди дає мені формат BGR, навіть якщо я зберігаю зображення (зсередини свого додатку в
кольоровому

1
@Soumyaljit Це буде копіювати дані у форматі, в якому спочатку зберігалися дані CGImageRef. У більшості випадків це буде BGRA, але може бути і YUV.
Камерон Лоуелл Палмер

18

Не міг повірити, що тут немає жодної правильної відповіді . Не потрібно виділяти покажчики, і незмножені значення ще потрібно нормалізувати. Щоб вирізати погоню, ось правильна версія Swift 4. Для UIImageпростого використання .cgImage.

extension CGImage {
    func colors(at: [CGPoint]) -> [UIColor]? {
        let colorSpace = CGColorSpaceCreateDeviceRGB()
        let bytesPerPixel = 4
        let bytesPerRow = bytesPerPixel * width
        let bitsPerComponent = 8
        let bitmapInfo: UInt32 = CGImageAlphaInfo.premultipliedLast.rawValue | CGBitmapInfo.byteOrder32Big.rawValue

        guard let context = CGContext(data: nil, width: width, height: height, bitsPerComponent: bitsPerComponent, bytesPerRow: bytesPerRow, space: colorSpace, bitmapInfo: bitmapInfo),
            let ptr = context.data?.assumingMemoryBound(to: UInt8.self) else {
            return nil
        }

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

        return at.map { p in
            let i = bytesPerRow * Int(p.y) + bytesPerPixel * Int(p.x)

            let a = CGFloat(ptr[i + 3]) / 255.0
            let r = (CGFloat(ptr[i]) / a) / 255.0
            let g = (CGFloat(ptr[i + 1]) / a) / 255.0
            let b = (CGFloat(ptr[i + 2]) / a) / 255.0

            return UIColor(red: r, green: g, blue: b, alpha: a)
        }
    }
}

Причина, що ви повинні намалювати / перетворити зображення спочатку в буфер, полягає в тому, що зображення можуть мати кілька різних форматів. Цей крок необхідний для перетворення його у послідовний формат, який ви можете прочитати.


Як я можу використовувати це розширення для свого зображення? нехай imageObj = UIImage (названий: "greencolorImg.png")
Sagar Sukode

let imageObj = UIImage(named:"greencolorImg.png"); imageObj.cgImage?.colors(at: [pt1, pt2])
Ерік Егнер

пам’ятайте, що це pt1і pt2є CGPointзмінними.
AnBisw

врятував мій день :)
Дарі

1
@ErikAigner це спрацює, якщо я хотів отримати колір кожного пікселя зображення? яка вартість продуктивності?
Ameet Dhas

9

Ось нитка SO, де @Matt надає лише потрібний піксель у контекст 1x1, переміщуючи зображення так, щоб потрібний піксель вирівнювався з одним пікселем у контексті.


9
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);

Я розмістив це, і це працювало для мене, але, переходячи з буфера назад до UIImage, я, здається, ще не зрозумів, якісь ідеї?
Nidal Fakhouri

8

UIImage - це обгортка, байтами є CGImage або CIImage

Згідно з Apple Reference on UIImage, об'єкт є незмінним і у вас немає доступу до резервних байтів. Хоча це правда , що ви можете отримати доступ до даних CGImage , якщо ви заселили UIImageз CGImage(явно або неявно), він повернеться , NULLякщо UIImageпідкріплений CIImageі навпаки.

Об'єкти зображень не забезпечують прямого доступу до базових даних зображення. Однак ви можете отримати дані зображення в інших форматах для використання у вашій програмі. Зокрема, ви можете використовувати властивості cgImage та ciImage для отримання версій зображення, сумісних із Core Graphics та Core Image відповідно. Ви також можете використовувати функції UIImagePNGRepresentation ( :) та UIImageJPEGRepresentation ( : _ :) для створення об’єкта NSData, що містить дані зображення у форматі PNG або JPEG.

Поширені хитрощі для подолання цього питання

Як зазначено, ваші варіанти є

  • Представлення UIImagePNGR або JPEG
  • Визначте, чи має зображення резервне копіювання даних CGImage або CIImage, і отримайте їх там

Жоден з цих особливо непоганих хитрощів, якщо ви хочете виводити дані, які не є ARGB, PNG або JPEG, і дані вже не підкріплені CIImage.

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

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

- візуалізація: toBitmap: rowBytes: bounds: format: colorSpace:

Як додатковий бонус, ви можете почати робити приємні маніпуляції із зображенням, прив'язуючи фільтри до зображення. Це вирішує багато питань, коли зображення перевернуте вгору або його потрібно повертати / масштабувати тощо.


6

Спираючись на відповідь Олі та Алгала, ось оновлена ​​відповідь для 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
}


3

Версія Swift 5

Надані тут відповіді застарілі або неправильні, оскільки вони не враховують наступне:

  1. Розмір пікселя зображення може відрізнятися від розміру точки, який повертається image.size.width/ image.size.height.
  2. У зображенні можуть бути різні макети, які використовуються піксельними компонентами, такими як BGRA, ABGR, ARGB тощо, або взагалі не можуть бути альфа-компоненти, такі як BGR та RGB. Наприклад, UIView.drawHierarchy(in:afterScreenUpdates:)метод може створювати зображення BGRA.
  3. Кольорові компоненти можна попередньо помножити на альфа для всіх пікселів на зображенні та їх потрібно розділити на альфа, щоб відновити початковий колір.
  4. Для оптимізації пам'яті, використовуваної CGImage, розмір піксельної рядки в байтах може бути більшим, ніж просто множення ширини пікселя на 4.

Код нижче полягає у наданні універсального рішення Swift 5, щоб отримати UIColorпіксель для всіх таких особливих випадків. Код оптимізовано для зручності використання та чіткості, а не для продуктивності.

public extension UIImage {

    var pixelWidth: Int {
        return cgImage?.width ?? 0
    }

    var pixelHeight: Int {
        return cgImage?.height ?? 0
    }

    func pixelColor(x: Int, y: Int) -> UIColor {
        assert(
            0..<pixelWidth ~= x && 0..<pixelHeight ~= y,
            "Pixel coordinates are out of bounds")

        guard
            let cgImage = cgImage,
            let data = cgImage.dataProvider?.data,
            let dataPtr = CFDataGetBytePtr(data),
            let colorSpaceModel = cgImage.colorSpace?.model,
            let componentLayout = cgImage.bitmapInfo.componentLayout
        else {
            assertionFailure("Could not get a pixel of an image")
            return .clear
        }

        assert(
            colorSpaceModel == .rgb,
            "The only supported color space model is RGB")
        assert(
            cgImage.bitsPerPixel == 32 || cgImage.bitsPerPixel == 24,
            "A pixel is expected to be either 4 or 3 bytes in size")

        let bytesPerRow = cgImage.bytesPerRow
        let bytesPerPixel = cgImage.bitsPerPixel/8
        let pixelOffset = y*bytesPerRow + x*bytesPerPixel

        if componentLayout.count == 4 {
            let components = (
                dataPtr[pixelOffset + 0],
                dataPtr[pixelOffset + 1],
                dataPtr[pixelOffset + 2],
                dataPtr[pixelOffset + 3]
            )

            var alpha: UInt8 = 0
            var red: UInt8 = 0
            var green: UInt8 = 0
            var blue: UInt8 = 0

            switch componentLayout {
            case .bgra:
                alpha = components.3
                red = components.2
                green = components.1
                blue = components.0
            case .abgr:
                alpha = components.0
                red = components.3
                green = components.2
                blue = components.1
            case .argb:
                alpha = components.0
                red = components.1
                green = components.2
                blue = components.3
            case .rgba:
                alpha = components.3
                red = components.0
                green = components.1
                blue = components.2
            default:
                return .clear
            }

            // If chroma components are premultiplied by alpha and the alpha is `0`,
            // keep the chroma components to their current values.
            if cgImage.bitmapInfo.chromaIsPremultipliedByAlpha && alpha != 0 {
                let invUnitAlpha = 255/CGFloat(alpha)
                red = UInt8((CGFloat(red)*invUnitAlpha).rounded())
                green = UInt8((CGFloat(green)*invUnitAlpha).rounded())
                blue = UInt8((CGFloat(blue)*invUnitAlpha).rounded())
            }

            return .init(red: red, green: green, blue: blue, alpha: alpha)

        } else if componentLayout.count == 3 {
            let components = (
                dataPtr[pixelOffset + 0],
                dataPtr[pixelOffset + 1],
                dataPtr[pixelOffset + 2]
            )

            var red: UInt8 = 0
            var green: UInt8 = 0
            var blue: UInt8 = 0

            switch componentLayout {
            case .bgr:
                red = components.2
                green = components.1
                blue = components.0
            case .rgb:
                red = components.0
                green = components.1
                blue = components.2
            default:
                return .clear
            }

            return .init(red: red, green: green, blue: blue, alpha: UInt8(255))

        } else {
            assertionFailure("Unsupported number of pixel components")
            return .clear
        }
    }

}

public extension UIColor {

    convenience init(red: UInt8, green: UInt8, blue: UInt8, alpha: UInt8) {
        self.init(
            red: CGFloat(red)/255,
            green: CGFloat(green)/255,
            blue: CGFloat(blue)/255,
            alpha: CGFloat(alpha)/255)
    }

}

public extension CGBitmapInfo {

    enum ComponentLayout {

        case bgra
        case abgr
        case argb
        case rgba
        case bgr
        case rgb

        var count: Int {
            switch self {
            case .bgr, .rgb: return 3
            default: return 4
            }
        }

    }

    var componentLayout: ComponentLayout? {
        guard let alphaInfo = CGImageAlphaInfo(rawValue: rawValue & Self.alphaInfoMask.rawValue) else { return nil }
        let isLittleEndian = contains(.byteOrder32Little)

        if alphaInfo == .none {
            return isLittleEndian ? .bgr : .rgb
        }
        let alphaIsFirst = alphaInfo == .premultipliedFirst || alphaInfo == .first || alphaInfo == .noneSkipFirst

        if isLittleEndian {
            return alphaIsFirst ? .bgra : .abgr
        } else {
            return alphaIsFirst ? .argb : .rgba
        }
    }

    var chromaIsPremultipliedByAlpha: Bool {
        let alphaInfo = CGImageAlphaInfo(rawValue: rawValue & Self.alphaInfoMask.rawValue)
        return alphaInfo == .premultipliedFirst || alphaInfo == .premultipliedLast
    }

}

Як щодо змін орієнтації? Вам також не потрібно перевіряти UIImage.Orientation?
endavid

Крім того , ваш componentLayoutвідсутній випадок , коли є тільки альфа, .alphaOnly.
ендавід

@endavid "Тільки альфа" не підтримується (оскільки це дуже рідкісний випадок). Орієнтація на зображення виходить за межі цього коду, але є достатньо ресурсів щодо нормалізації орієнтації а UIImage, що саме ви зробите, перш ніж почати читати його кольори пікселів.
Десмонд Юм

2

На основі різних відповідей, але в основному на цьому , це працює для того, що мені потрібно:

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.


1

Для доступу до необроблених значень RGB UIImage в Swift 5 використовуйте базовий CGImage та його dataProvider:

import UIKit

let image = UIImage(named: "example.png")!

guard let cgImage = image.cgImage,
    let data = cgImage.dataProvider?.data,
    let bytes = CFDataGetBytePtr(data) else {
    fatalError("Couldn't access image data")
}
assert(cgImage.colorSpace?.model == .rgb)

let bytesPerPixel = cgImage.bitsPerPixel / cgImage.bitsPerComponent
for y in 0 ..< cgImage.height {
    for x in 0 ..< cgImage.width {
        let offset = (y * cgImage.bytesPerRow) + (x * bytesPerPixel)
        let components = (r: bytes[offset], g: bytes[offset + 1], b: bytes[offset + 2])
        print("[x:\(x), y:\(y)] \(components)")
    }
    print("---")
}

https://www.ralfebert.de/ios/examples/image-processing/uiimage-raw-pixels/

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.