Обрезать UIImage до альфа

У меня есть довольно большое, почти полноэкранное изображение, которое я собираюсь отображать на iPad. Изображение прозрачно примерно на 80%. Мне нужно на клиенте определить ограничивающую рамку непрозрачных пикселей, а затем обрезать до этой ограничивающей рамки.

Просматривая другие вопросы здесь, в StackOverflow, и читая некоторые документы CoreGraphics, я думаю, что мог бы сделать это следующим образом:

CGBitmapContextCreate(...) // Use this to render the image to a byte array

 ..
   - iterate through this byte array to find the bounding box
 ..

CGImageCreateWithImageInRect(image, boundingRect);

Это только кажется очень неэффективным и неуклюжим. Есть ли что-то умное, что я могу сделать с масками CGImage или что-то, что использует ускорение графики устройства для этого?


person MikeQ    schedule 29.06.2011    source источник
comment
Вы пробовали это на устройстве? Бьюсь об заклад, это будет быстрее, чем вы думаете.   -  person Benjamin Mayo    schedule 10.07.2011
comment
Это правда - как только я действительно сел и реализовал это, время обработки было намного быстрее, чем я думал!   -  person MikeQ    schedule 16.09.2011


Ответы (3)


Не существует хитрых схем, позволяющих заставить устройство выполнять работу, но есть несколько способов ускорить выполнение задачи или свести к минимуму влияние на пользовательский интерфейс.

Во-первых, рассмотреть необходимость ускорения этой задачи. Простая итерация по этому массиву байтов может пройти достаточно быстро. Возможно, нет необходимости вкладывать средства в оптимизацию этой задачи, если приложение просто вычисляет это один раз за запуск или в ответ на выбор пользователя, который занимает не менее нескольких секунд между выборами.

Если ограничительная рамка не нужна какое-то время после того, как изображение стало доступным, эту итерацию можно запустить в отдельном потоке. Таким образом, вычисление не блокирует основной поток интерфейса. Grand Central Dispatch может упростить использование отдельного потока для этой задачи.

Если задачу надо ускорить, может это обработка видеоизображений в реальном времени, то может помочь параллельная обработка данных. Инфраструктура Accelerate может помочь в настройке вычислений SIMD для данных. Или, чтобы действительно получить производительность с этой итерацией, код на языке ассемблера ARM, использующий операции NEON SIMD, может дать отличные результаты при значительных усилиях по разработке.

Последний выбор - исследовать лучший алгоритм. Существует огромный объем работы по обнаружению признаков на изображениях. Алгоритм обнаружения границ может быть быстрее, чем простая итерация по массиву байтов. Возможно, в будущем Apple добавит возможности обнаружения краев в Core Graphics, которые можно будет применить в этом случае. Возможности обработки изображений, реализованные Apple, могут не полностью соответствовать этому случаю, но реализация Apple должна быть оптимизирована для использования возможностей SIMD или GPU iPad, что приведет к повышению общей производительности.

person Mr. Berna    schedule 11.07.2011

Спасибо пользователю 404709 за всю тяжелую работу. Код ниже также обрабатывает изображения Retina и освобождает CFDataRef.

- (UIImage *)trimmedImage {

    CGImageRef inImage = self.CGImage;
    CFDataRef m_DataRef;
    m_DataRef = CGDataProviderCopyData(CGImageGetDataProvider(inImage));

    UInt8 * m_PixelBuf = (UInt8 *) CFDataGetBytePtr(m_DataRef);

    size_t width = CGImageGetWidth(inImage);
    size_t height = CGImageGetHeight(inImage);

    CGPoint top,left,right,bottom;

    BOOL breakOut = NO;
    for (int x = 0;breakOut==NO && x < width; x++) {
        for (int y = 0; y < height; y++) {
            int loc = x + (y * width);
            loc *= 4;
            if (m_PixelBuf[loc + 3] != 0) {
                left = CGPointMake(x, y);
                breakOut = YES;
                break;
            }
        }
    }

    breakOut = NO;
    for (int y = 0;breakOut==NO && y < height; y++) {

        for (int x = 0; x < width; x++) {

            int loc = x + (y * width);
            loc *= 4;
            if (m_PixelBuf[loc + 3] != 0) {
                top = CGPointMake(x, y);
                breakOut = YES;
                break;
            }

        }
    }

    breakOut = NO;
    for (int y = height-1;breakOut==NO && y >= 0; y--) {

        for (int x = width-1; x >= 0; x--) {

            int loc = x + (y * width);
            loc *= 4;
            if (m_PixelBuf[loc + 3] != 0) {
                bottom = CGPointMake(x, y);
                breakOut = YES;
                break;
            }

        }
    }

    breakOut = NO;
    for (int x = width-1;breakOut==NO && x >= 0; x--) {

        for (int y = height-1; y >= 0; y--) {

            int loc = x + (y * width);
            loc *= 4;
            if (m_PixelBuf[loc + 3] != 0) {
                right = CGPointMake(x, y);
                breakOut = YES;
                break;
            }

        }
    }


    CGFloat scale = self.scale;

    CGRect cropRect = CGRectMake(left.x / scale, top.y/scale, (right.x - left.x)/scale, (bottom.y - top.y) / scale);
    UIGraphicsBeginImageContextWithOptions( cropRect.size,
                                           NO,
                                           scale);
    [self drawAtPoint:CGPointMake(-cropRect.origin.x, -cropRect.origin.y)
           blendMode:kCGBlendModeCopy
               alpha:1.];
    UIImage *croppedImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    CFRelease(m_DataRef);
    return croppedImage;
}
person Stephane JAIS    schedule 27.09.2012
comment
Я только что реализовал это в своей программе - это именно то, что мне нужно! Но я получаю EXC_BAD_ACCESS на первом if (m_PixelBuf[loc + 3] != 0) { line. Есть идеи, почему? Спасибо. - person Smikey; 06.02.2014
comment
@Smikey, пытаетесь использовать это на изображении, снятом с камеры? Если да, то в соответствии с этим [stackoverflow.com/a/12614015/1541893] пользователем1702121, CGBitmapContextGetData() в созданном контексте из захваченного буфера сэмплов не возвращает его растровое изображение. Он предлагает решение. - person Dan; 29.03.2014

Я создал категорию на UImage, которая делает это, если кому-то это нужно...

+ (UIImage *)cropTransparencyFromImage:(UIImage *)img {

    CGImageRef inImage = img.CGImage;           
    CFDataRef m_DataRef;  
    m_DataRef = CGDataProviderCopyData(CGImageGetDataProvider(inImage));  
    UInt8 * m_PixelBuf = (UInt8 *) CFDataGetBytePtr(m_DataRef);  

    int width = img.size.width;
    int height = img.size.height;

    CGPoint top,left,right,bottom;

    BOOL breakOut = NO;
    for (int x = 0;breakOut==NO && x < width; x++) {
        for (int y = 0; y < height; y++) {
            int loc = x + (y * width);
            loc *= 4;
            if (m_PixelBuf[loc + 3] != 0) {
                left = CGPointMake(x, y);
                breakOut = YES;
                break;
            }
        }
    }

    breakOut = NO;
    for (int y = 0;breakOut==NO && y < height; y++) {

        for (int x = 0; x < width; x++) {

            int loc = x + (y * width);
            loc *= 4;
            if (m_PixelBuf[loc + 3] != 0) {
                top = CGPointMake(x, y);
                breakOut = YES;
                break;
            }

        }
    }

    breakOut = NO;
    for (int y = height-1;breakOut==NO && y >= 0; y--) {

        for (int x = width-1; x >= 0; x--) {

            int loc = x + (y * width);
            loc *= 4;
            if (m_PixelBuf[loc + 3] != 0) {
                bottom = CGPointMake(x, y);
                breakOut = YES;
                break;
            }

        }
    }

    breakOut = NO;
    for (int x = width-1;breakOut==NO && x >= 0; x--) {

        for (int y = height-1; y >= 0; y--) {

            int loc = x + (y * width);
            loc *= 4;
            if (m_PixelBuf[loc + 3] != 0) {
                right = CGPointMake(x, y);
                breakOut = YES;
                break;
            }

        }
    }


    CGRect cropRect = CGRectMake(left.x, top.y, right.x - left.x, bottom.y - top.y);

    UIGraphicsBeginImageContextWithOptions( cropRect.size,
                                           NO,
                                           0.);
    [img drawAtPoint:CGPointMake(-cropRect.origin.x, -cropRect.origin.y)
           blendMode:kCGBlendModeCopy
               alpha:1.];
    UIImage *croppedImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return croppedImage;
}
person Matt Green    schedule 09.02.2012
comment
Спасибо за этот скрипт. Я использую его в своем проекте, но заметил проблему с утечкой памяти. Я думаю, нам нужно вызвать CFRelease(m_DataRef); перед возвратом кадрированного изображения; - person Wayne Liu; 09.04.2012
comment
Спасибо за этот полезный ответ user404. Знаете ли вы, есть ли способ после того, как вы извлекли точки, применить к ним преобразование? - person Ser Pounce; 08.02.2013
comment
Просто повторяю свой комментарий на случай, если другие смотрят: я только что реализовал это в своей программе - это именно то, что мне нужно! Но я получаю EXC_BAD_ACCESS на первом if (m_PixelBuf[loc + 3] != 0) { line. Есть идеи, почему? Спасибо - person Smikey; 07.02.2014
comment
@foolishBoy обнаружил, что лево, право, верх и низ неверны, что приводит к некоторым черным краям, когда изображение меньше прямоугольника обрезки. - i.stack.imgur.com/sFxlr.jpg - i.stack.imgur.com/JOz1Z.jpg - person J. Chomel; 29.06.2016