AzoftБлогiOS: расширенное использование UIImage

iOS: расширенное использование UIImage

Андрей Чевозеров Апрель 1, 2013

Ни одна разработка iOS-приложения не обходится без UIImage. Это и иконки кнопок, и фоновые изображения, и многое другое. Но не все разработчики знают, каким мощным и функциональным инструментом может стать это простой объект, если подойти к нему с правильной стороны. Несколько таких «сторон» я вам сегодня раскрою. Забегая вперед скажу, что речь пойдет о заливке фона текстурой, создании «резиновых» элементов интерфейса, к примеру, кнопок, изменении изображений и наложении масок.

Начну с функции «Замостить». Можно долго изучать документацию к UIImage и UIImageView, но так и не найти ответа на вопрос, как размножить картинку в пределах заданного поля. Ответ может показаться странным — нам нужен UIColor, точнее, его метод colorWithPatternImage. Нижеприведенный код заливает фон текущего изображения текстурой:

UIImage *pattern = [UIImage imageNamed:@"TextureImage"];
self.view.backgroundColor = [UIColor colorWithPatternImage:pattern];

Исходное изображение:

Результат заливки:

Следующая достаточно полезная возможность UIImage  — создание «резиновых» картинок.

Допустим, нужно нам сделать несколько кнопок одинакового вида, но разного размера, или повесить значок по типу того, что висит над иконками приложений или показывается в панели вкладок. Чтобы не создавать каждую кнопку отдельно, а сделать, чтобы изображения сами подстраивались под нужный размер, просто пропишем:

UIImage *buttonBackground = [UIImage imageNamed:@"Button"];
buttonBackground = [buttonBackground stretchableImageWithLeftCapWidth:15 topCapHeight:0];
[self.logoutButton setBackgroundImage:buttonBackground forState:UIControlStateNormal];

Изображение кнопки в ресурсах:

Результат:

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

Кстати, некоторые контроллеры, например UIImageView, позволяют задавать параметры растяжения прямо в InterfaceBuilder (группа «Stretching» раздела «View», вкладке «Attributes»). Работает это не для всех элементов UI, и те же кнопки никак не реагируют на изменение этих параметров, но в целом все довольно просто.

От простых задач переходим к более серьезным. Самое время разобраться, как изменять картинки и накладывать маски. Для примера возьмем задачу отобразить аватарку пользователя в кружочке с рамкой, причем исходная картинка квадратная и без рамки.

Итак, нам понадобятся три изображения: аватарка, рамка и маска:

  

  

Как вы видите, аватарка у нас заметно больше рамки, так что размер изображения придется тоже поменять. Поэтому сначала создадим новую категорию для UIImage (например, «Operations») и добавим туда следующий метод:

- (UIImage *)imageScaledToSize:(CGSize)size
{
    if (UIGraphicsBeginImageContextWithOptions != NULL) {
        UIGraphicsBeginImageContextWithOptions(size, NO, 0.0);
    } else {
        UIGraphicsBeginImageContext(size);
    }

    [self drawInRect:CGRectMake(0.0, 0.0, size.width, size.height)];

    UIImage *result = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();

    return result;
}

Теперь в коде можно использовать следующую конструкцию:

UIImage *avatarImage = [UIImage imageNamed:@"UserAvatar.jpg"];
self.avatarImageView.image = [avatarImage imageScaledToSize:self.avatarImageView.frame.size];

В результате у нас получилось:

Чтобы контроллер не пытался сам растягивать аватарку, у avatarImageView мы выставили режим отображения «Center». Теперь у нас есть картинка нужного размера, осталось вырезать из нее кружок и наложить рамку.

На самом деле, мы не будем ничего вырезать, а применим к изображению маску. Маска — это специальное черно-белое изображение, совпадающее по размерам с оригинальным. Обращаю ваше внимание, что изображение должно быть именно черно-белым (режим Grayscale в Photoshop). Это очень важный момент, иначе фокус не удастся. Чем чернее пиксел, тем менее прозрачным будет изображение под маской. Т.е. совсем белый — полная прозрачность, серый — полупрозрачность, совсем черный — нет прозрачности. К изображению маски есть еще одно требование: в нем не должно быть прозрачности, т.е. альфа-канал необходимо отключать. Иначе маска не будет применена к исходному изображению. Идеальный вариант — сохранить изображение маски в формате, который не поддерживает прозрачность вообще, например JPG.

Чтобы наложить маску, добавим в нашу категорию следующий метод:

- (UIImage *)imageWithMask:(UIImage *)maskImage
{
    CGImageRef maskRef = maskImage.CGImage;
    CGImageRef mask = CGImageMaskCreate(CGImageGetWidth(maskRef),
                                        CGImageGetHeight(maskRef),
CGImageGetBitsPerComponent(maskRef),
CGImageGetBitsPerPixel(maskRef),
CGImageGetBytesPerRow(maskRef),
CGImageGetDataProvider(maskRef), NULL, false);

    CGImageRef maskedImageRef = CGImageCreateWithMask(self.CGImage, mask);
    UIImage *maskedImage = [UIImage imageWithCGImage:maskedImageRef scale:self.scale orientation:UIImageOrientationUp];

    CGImageRelease(mask);
    CGImageRelease(maskedImageRef);

    return maskedImage;
}

Дорабатываем наш код в ViewController:

UIImage *avatarImage = [UIImage imageNamed:@"UserAvatar.jpg"]; avatarImage = [avatarImage imageScaledToSize:self.avatarImageView.frame.size]; avatarImage = [avatarImage imageWithMask:[UIImage imageNamed:@"AvatarMask"]]; self.avatarImageView.image = avatarImage;

В итоге получаем:

Есть, правда, один небольшой подводный камень, но о нем я расскажу чуть позже.

Наконец, делаем завершающий штрих — добавим рамку. В этом нам поможет следующий метод:

- (UIImage *)imageByAddingImage:(UIImage *)image
{
    CGSize firstSize = self.size;
    CGSize secondSize = image.size;

    CGSize mergedSize = CGSizeMake(MAX(firstSize.width, secondSize.width),
                                   MAX(firstSize.height, secondSize.height));
    CGFloat mergedScale = MAX(self.scale, image.scale);

    UIGraphicsBeginImageContextWithOptions(mergedSize, NO, mergedScale);

    [self drawInRect:CGRectMake(0.0, 0.0, self.size.width, self.size.height)];
    [image drawInRect:CGRectMake(0.0, 0.0, image.size.width, image.size.height)];

    UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();

    return newImage;
}

Снова дорабатываем контроллер:

UIImage *avatarImage = [UIImage imageNamed:@"UserAvatar.jpg"]; avatarImage = [avatarImage imageScaledToSize:self.avatarImageView.frame.size]; avatarImage = [avatarImage imageWithMask:[UIImage imageNamed:@"AvatarMask"]]; avatarImage = [avatarImage imageByAddingImage:[UIImage imageNamed:@"AvatarFrame"]]; self.avatarImageView.image = avatarImage;

Вот результат всех наших усилий:

А теперь тот самый подводный камень, о котором я говорил. Иногда бывает ситуация, когда исходное изображение по каким-то причинам не имеет альфа-канала. Обычно он и не требуется, но в процессе обработки такого изображения могут возникать нежелательные артефакты в виде черных полигонов там, где должна быть прозрачность. В тестовом проекте воссоздать такую ситуацию не удалось, но в реальности такие случаи встречаются, в основном на iOS 4.3 (такое ощущение, что в фреймворках Apple завелся баг, но в 5.0 его уже исправили).

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

Чтобы добавить альфа-канал, доработаем нашу категорию следующими методами:

// проверка наличия альфа-канала 

- (BOOL)hasAlpha {     CGImageAlphaInfo alpha = CGImageGetAlphaInfo(self.CGImage);     return (alpha == kCGImageAlphaFirst ||             alpha == kCGImageAlphaLast ||             alpha == kCGImageAlphaPremultipliedFirst ||             alpha == kCGImageAlphaPremultipliedLast); } 

// добавление альфа-канала к изображению - (UIImage *)imageWithAlpha {     if ([self hasAlpha]) {         returnself;     }          CGImageRef imageRef = self.CGImage;     size_t width = CGImageGetWidth(imageRef);     size_t height = CGImageGetHeight(imageRef);          

// The bitsPerComponent and bitmapInfo values are hard-coded to prevent an "unsupported parameter combination" error     CGContextRef offscreenContext = CGBitmapContextCreate(NULL,                                                        width,                                                        height,                                                        8,                                                        0,                                                        CGImageGetColorSpace(imageRef),                                                        kCGBitmapByteOrderDefault | kCGImageAlphaPremultipliedFirst);          CGContextDrawImage(offscreenContext, CGRectMake(0, 0, width, height), imageRef);     CGImageRef imageRefWithAlpha = CGBitmapContextCreateImage(offscreenContext);     UIImage *imageWithAlpha = [UIImage imageWithCGImage:imageRefWithAlpha];          CGContextRelease(offscreenContext);     CGImageRelease(imageRefWithAlpha);          return imageWithAlpha; }

На сегодня это все скрытые возможности UIImage, которые мне удалось обнаружить. Не удивлюсь, если скоро найдутся новые, и расскажу о них в следующих статьях. Следите за обновлениями!

Комментарии

комментарии



Content created by Andrey Chevozerov