Как тонировать прозрачное изображение PNG в iPhone? - PullRequest
73 голосов
/ 18 августа 2010

Я знаю, что можно тонировать прямоугольное изображение, рисуя поверх него CGContextFillRect и устанавливая режим наложения. Однако я не могу понять, как сделать оттенок на прозрачном изображении, таком как значок. Это должно быть возможно, так как SDK сам делает это на вкладках. Кто-нибудь сможет предоставить фрагмент?

UPDATE:

Много замечательных предложений было дано для этой проблемы, так как я первоначально спросил. Обязательно прочитайте все ответы, чтобы выяснить, что подходит вам больше всего.

ОБНОВЛЕНИЕ (30 апреля 2015 г.):

С iOS 7.0 я теперь могу просто сделать следующее, что удовлетворит потребности моего первоначального вопроса. Но если у вас есть более сложные случаи, проверьте все ответы.

UIImage *iconImage = [[UIImage imageNamed:@"myImageName"] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];    
UIImageView *icon = [[UIImageView alloc] initWithImage:iconImage];
icon.tintColor = [UIColor redColor];

Ответы [ 13 ]

163 голосов
/ 11 сентября 2011

Обновление: Вот Суть для расширения Swift UIColor с использованием приведенного ниже кода.


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

Напротив, если у вас есть изображение не в оттенках серого , ИЛИ у вас есть блики и тени , которые должны быть сохраняется режим смешивания kCGBlendModeColor. Белый останется белым, а черный останется черным, так как яркость изображения сохранится. Этот режим предназначен только для тонировки - он такой же, как и режим наложения слоя Color в Photoshop (отказ от ответственности: могут быть немного отличающиеся результаты).

Обратите внимание, что тонировка альфа-пикселей не работает правильно ни в iOS, ни в Photoshop - полупрозрачные черные пиксели не будут оставаться черными. Я обновил ответ ниже, чтобы обойти эту проблему, потребовалось довольно много времени, чтобы выяснить.

Вы также можете использовать один из режимов наложения kCGBlendModeSourceIn/DestinationIn вместо CGContextClipToMask.

Если вы хотите создать UIImage, каждый из следующих разделов кода может быть заключен в следующий код:

UIGraphicsBeginImageContextWithOptions (myIconImage.size, NO, myIconImage.scale); // for correct resolution on retina, thanks @MobileVet
CGContextRef context = UIGraphicsGetCurrentContext();

CGContextTranslateCTM(context, 0, myIconImage.size.height);
CGContextScaleCTM(context, 1.0, -1.0);

CGRect rect = CGRectMake(0, 0, myIconImage.size.width, myIconImage.size.height);

// image drawing code here

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

Итак, вот код для тонирования прозрачного изображения с помощью kCGBlendModeColor:

// draw black background to preserve color of transparent pixels
CGContextSetBlendMode(context, kCGBlendModeNormal);
[[UIColor blackColor] setFill];
CGContextFillRect(context, rect);

// draw original image
CGContextSetBlendMode(context, kCGBlendModeNormal);
CGContextDrawImage(context, rect, myIconImage.CGImage);

// tint image (loosing alpha) - the luminosity of the original image is preserved
CGContextSetBlendMode(context, kCGBlendModeColor);
[tintColor setFill];
CGContextFillRect(context, rect);

// mask by alpha values of original image
CGContextSetBlendMode(context, kCGBlendModeDestinationIn);
CGContextDrawImage(context, rect, myIconImage.CGImage);

Если ваше изображение не имеет полупрозрачных пикселей, вы также можете сделать это с помощью kCGBlendModeLuminosity:

// draw tint color
CGContextSetBlendMode(context, kCGBlendModeNormal);
[tintColor setFill];
CGContextFillRect(context, rect);

// replace luminosity of background (ignoring alpha)
CGContextSetBlendMode(context, kCGBlendModeLuminosity);
CGContextDrawImage(context, rect, myIconImage.CGImage);

// mask by alpha values of original image
CGContextSetBlendMode(context, kCGBlendModeDestinationIn);
CGContextDrawImage(context, rect, myIconImage.CGImage);

Если вам не нужна яркость, поскольку у вас есть изображение с альфа-каналом, которое должно быть окрашено в цвет, вы можете сделать это более эффективным способом:

// draw tint color
CGContextSetBlendMode(context, kCGBlendModeNormal);
[tintColor setFill];
CGContextFillRect(context, rect);

// mask by alpha values of original image
CGContextSetBlendMode(context, kCGBlendModeDestinationIn);
CGContextDrawImage(context, rect, myIconImage.CGImage);

или наоборот:

// draw alpha-mask
CGContextSetBlendMode(context, kCGBlendModeNormal);
CGContextDrawImage(context, rect, myIconImage.CGImage);

// draw tint color, preserving alpha values of original image
CGContextSetBlendMode(context, kCGBlendModeSourceIn);
[tintColor setFill];
CGContextFillRect(context, rect);

Веселись!

21 голосов
/ 20 марта 2014

У меня был наибольший успех с этим методом, потому что другие, которые я пробовал, вызывали искаженные цвета для полупрозрачных пикселей для определенных цветовых комбинаций.Это также должно быть немного лучше с точки зрения производительности.

+ (UIImage *) imageNamed:(NSString *) name withTintColor: (UIColor *) tintColor {

    UIImage *baseImage = [UIImage imageNamed:name];

    CGRect drawRect = CGRectMake(0, 0, baseImage.size.width, baseImage.size.height);

    UIGraphicsBeginImageContextWithOptions(baseImage.size, NO, 0);
    CGContextRef context = UIGraphicsGetCurrentContext();

    CGContextTranslateCTM(context, 0, baseImage.size.height);
    CGContextScaleCTM(context, 1.0, -1.0);

    // draw original image
    CGContextSetBlendMode(context, kCGBlendModeNormal);
    CGContextDrawImage(context, drawRect, baseImage.CGImage);

    // draw color atop
    CGContextSetFillColorWithColor(context, tintColor.CGColor);
    CGContextSetBlendMode(context, kCGBlendModeSourceAtop);
    CGContextFillRect(context, drawRect);

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

    return tintedImage;
}
18 голосов
/ 19 августа 2010

После поисков, лучшее решение, которое я нашел до сих пор, состоит в том, чтобы использовать комбинацию режима наложения и обтравочной маски для достижения раскрашивания / тонирования прозрачного PNG:

CGContextSetBlendMode (context, kCGBlendModeMultiply);

CGContextDrawImage(context, rect, myIconImage.CGImage);

CGContextClipToMask(context, rect, myIconImage.CGImage);

CGContextSetFillColorWithColor(context, tintColor);

CGContextFillRect(context, rect);
16 голосов
/ 29 марта 2012

Я могу получить результаты очень близко к оттенку в панели навигации Apple, используя kCGBlendModeOverlay.Взяв отличный ответ @fabb и объединив подход @omz в этом посте https://stackoverflow.com/a/4684876/229019 Я пришел с этим решением, которое содержит ожидаемые результаты:

- (UIImage *)tintedImageUsingColor:(UIColor *)tintColor;
{
    UIGraphicsBeginImageContextWithOptions (self.size, NO, [[UIScreen mainScreen] scale]);

    CGContextRef context = UIGraphicsGetCurrentContext();
    CGRect rect = CGRectMake(0, 0, self.size.width, self.size.height);

    // draw original image
    [self drawInRect:rect blendMode:kCGBlendModeNormal alpha:1.0f];

    // tint image (loosing alpha). 
    // kCGBlendModeOverlay is the closest I was able to match the 
    // actual process used by apple in navigation bar 
    CGContextSetBlendMode(context, kCGBlendModeOverlay);
    [tintColor setFill];
    CGContextFillRect(context, rect);

    // mask by alpha values of original image
    [self drawInRect:rect blendMode:kCGBlendModeDestinationIn alpha:1.0f];

    UIImage *tintedImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return tintedImage;
}

Вот пример окрашивания нескольких изображений в оттенках серого с прозрачностью:

Rendered Example:

  • Первая строка - это окрашенная панель инструментов яблока [UIColor orangeColor].
  • Вторая строка - это один и тот же градиент, окрашенный в несколько цветов, начиная с чистого цвета (= фактического градиента) и заканчивая тем же оранжевым цветом.
  • Третий - простой круг с прозрачностью (цвет фона - лен)
  • Четвертая строка - сложная темная шумная текстура
8 голосов
/ 02 октября 2013

В iOS7 они ввели свойство tintColor в UIImageView и renderMode в UIImage.Смотрите мой пример на https://stackoverflow.com/a/19125120/1570970

8 голосов
/ 26 сентября 2013

Вы можете создать категорию UIImage и сделать это так:

- (instancetype)tintedImageWithColor:(UIColor *)tintColor {
    UIGraphicsBeginImageContextWithOptions(self.size, NO, 0.0);
    CGContextRef context = UIGraphicsGetCurrentContext();

    CGRect rect = (CGRect){ CGPointZero, self.size };
    CGContextSetBlendMode(context, kCGBlendModeNormal);
    [self drawInRect:rect];

    CGContextSetBlendMode(context, kCGBlendModeSourceIn);
    [tintColor setFill];
    CGContextFillRect(context, rect);

    UIImage *image  = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return image;
}
3 голосов
/ 30 апреля 2015

В iOS 7.0 вы также можете просто сделать это, чтобы подкрасить базовый UIImageView:

UIImage *iconImage = [[UIImage imageNamed:@"myImageName"] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];    
UIImageView *icon = [[UIImageView alloc] initWithImage:iconImage];
icon.tintColor = [UIColor redColor];
3 голосов
/ 11 июля 2013

Обратите внимание, что в принятом ответе от fabb «окружающий» код для создания UIImage дал мне неправильное разрешение изображений на экране сетчатки. Чтобы исправить, измените:

UIGraphicsBeginImageContext(myIconImage.size);

до:

UIGraphicsBeginImageContextWithOptions(myIconImage.size, NO, 0.0);

Последний параметр, который установлен в 0.0, является масштабом, и согласно документации Apple:

"Если указать значение 0,0, коэффициент масштабирования будет установлен на масштаб фактор основного экрана устройства ".

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

2 голосов
/ 18 августа 2010

UIImageView (или любое другое представление) имеет цвет фона RGBA.Альфа в цвете может делать то, что вам нужно, не изобретая что-то новое.

1 голос
/ 26 января 2016

Никакие ответы не помогут мне при переполнении стека: наши дизайнеры рисуют элементы пользовательского интерфейса с различными формами, различными альфа-значениями (и «альфа-дырами»). В большинстве случаев это 32-битный файл PNG с альфа-каналом, который содержит черно-белые пиксели (всех возможных интенсивностей). После окрашивания такой картинки мне пришлось получить такой тонированный результат: белые пиксели - тонированные больше, а темные пиксели - меньше. И все это с учетом альфа-канала. И я написал этот метод для моей категории UIImage. Может быть, это не очень эффективно, но это работает как часы :) Вот оно:

- (UIImage *)imageTintedWithColor:(UIColor *)color {
    UIGraphicsBeginImageContextWithOptions(self.size, NO, self.scale);
    CGContextRef context = UIGraphicsGetCurrentContext();

    CGRect rect = CGRectMake(0, 0, self.size.width, self.size.height);

    CGContextSetBlendMode(context, kCGBlendModeCopy);
    [color setFill];
    CGContextFillRect(context, rect);

    [self drawInRect:rect blendMode:kCGBlendModeXOR alpha:1.0];

    CGContextSetBlendMode(context, kCGBlendModeXOR);
    CGContextFillRect(context, rect);

    [self drawInRect:rect blendMode:kCGBlendModeMultiply alpha:1.0];

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

    return coloredImage;
}
...