Как программно подкрасить изображение на iOS?

87

Я хочу подкрасить изображение с помощью цветовой ссылки. Результат должен выглядеть как режим смешивания Multiply в Photoshop, где белые цвета будут заменены оттенком :

альтернативный текст

Я буду постоянно менять значение цвета.

Дальнейшие действия: я бы поместил код для этого в метод drawRect: моего ImageView, верно?

Как всегда, в моем понимании мне очень помогли бы фрагменты кода , а не ссылка.

Обновление: создание подкласса UIImageView с кодом, предложенным Рамином .

Я поместил это в viewDidLoad: моего контроллера представления:

[self.lena setImage:[UIImage imageNamed:kImageName]];
[self.lena setOverlayColor:[UIColor blueColor]];
[super viewDidLoad];

Я вижу изображение, но оно не тонируется. Я также попытался загрузить другие изображения, установить изображение в IB и вызвать setNeedsDisplay: в моем контроллере представления.

Обновление : drawRect: не вызывается.

Последнее обновление: я нашел старый проект, в котором был правильно настроен imageView, чтобы я мог протестировать код Рамина, и он работает как шарм!

Финальное, окончательное обновление:

Для тех из вас, кто только изучает Core Graphics, вот самая простая вещь, которая могла бы сработать.

В вашем подклассе UIView:

- (void)drawRect:(CGRect)rect {

    CGContextRef context = UIGraphicsGetCurrentContext();

    CGContextSetFillColor(context, CGColorGetComponents([UIColor colorWithRed:0.5 green:0.5 blue:0 alpha:1].CGColor)); // don't make color too saturated

    CGContextFillRect(context, rect); // draw base

    [[UIImage imageNamed:@"someImage.png"] drawInRect: rect blendMode:kCGBlendModeOverlay alpha:1.0]; // draw image
}
willc2
источник
Я добавил фрагмент перед отправкой в ​​какой-то старый код drawRect, который у меня был, и он работал нормально. Возможно, захотите попробовать без вызова [super viewDidLoad] (или переместите его выше). Также перепроверьте, чтобы убедиться, что тот, кто выделяет этот объект, выделяет производную версию, а не ванильный UIImageView (т.е. загружается ли она в перо, распределителе и т.д.).
Рамин,
Другое предложение, если вышеперечисленное не работает: вместо подкласса UIImageView подкласса простой UIView и добавьте как overlayColor, так и свойство UIImage под названием 'image', которое вы можете установить. Затем поместите туда drawRect. Код drawRect не заботится о том, исходит ли значение 'image' из UIImageView или из определенного вами свойства.
Рамин,
Разве это не сработает, если изображение с альфа-каналом? Что делать, если я хочу тонировать только нарисованную часть изображения? (Так же, как UIButton?)
Сунил Чаухан

Ответы:

45

Сначала вы хотите создать подкласс UIImageView и переопределить метод drawRect. Вашему классу требуется свойство UIColor (назовем его overlayColor) для хранения смешанного цвета и настраиваемый установщик, который вызывает перерисовку при изменении цвета. Что-то вроде этого:

- (void) setOverlayColor:(UIColor *)newColor {
   if (overlayColor)
     [overlayColor release];

   overlayColor = [newColor retain];
   [self setNeedsDisplay]; // fires off drawRect each time color changes
}

В методе drawRect вам нужно сначала нарисовать изображение, а затем наложить на него прямоугольник, залитый желаемым цветом, и соответствующий режим наложения, примерно так:

- (void) drawRect:(CGRect)area
{
  CGContextRef context = UIGraphicsGetCurrentContext();
  CGContextSaveGState(context);

  // Draw picture first
  //
  CGContextDrawImage(context, self.frame, self.image.CGImage);

  // Blend mode could be any of CGBlendMode values. Now draw filled rectangle
  // over top of image.
  //
  CGContextSetBlendMode (context, kCGBlendModeMultiply);
  CGContextSetFillColor(context, CGColorGetComponents(self.overlayColor.CGColor));      
  CGContextFillRect (context, self.bounds);
  CGContextRestoreGState(context);
}

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

Чтобы использовать его, создайте экземпляр объекта, а затем установите для imageсвойства (унаследованного от UIImageView) изображение и overlayColorзначение UIColor (уровни наложения можно настроить, изменив альфа-значение передаваемого вами цвета).

Рамин
источник
Дополнительные предложения прилагаются к исходному запросу (см. Выше).
Рамин,
следует добавить CGContextSaveGState (context); перед изменением режима наложения, иначе вы получите недополнение gstack.
willc2
Ооо, спасибо. Ошибка копирования / вставки. Я исправил это во фрагменте кода.
Рамин
10
(Как также указано в другом ответе здесь) Особые соображения Класс UIImageView оптимизирован для вывода изображений на дисплей. UIImageView не будет вызывать drawRect: подкласс. Если вашему подклассу требуется пользовательский код рисования, рекомендуется использовать UIView в качестве базового класса. Это означает, что вы не можете создать подкласс UIImageView и ожидать вызова drawRect вообще. .
Джонни
Привет, к сожалению, я попробовал код, и он не сработал: /. См. Ответ @mpstx ниже, в котором говорится, что drawRect не будет вызываться из UIImageView, и вместо этого вы должны использовать UIView (с UIImage), который отлично работал у меня.
jklp
77

В iOS7 они представили свойство tintColor в UIImageView и renderMode в UIImage. Чтобы подкрасить UIImage на iOS7, все, что вам нужно сделать, это:

UIImageView* imageView = …
UIImage* originalImage = …
UIImage* imageForRendering = [originalImage imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
imageView.image = imageForRendering;
imageView.tintColor = [UIColor redColor]; // or any color you want to tint it with
Толанд Хон
источник
18
Это определенно лучший и самый простой способ перейти на iOS 7, если вы ищете именно то поведение оттенка. Но этот код применяет оттенок, как если бы вы использовали 100% непрозрачный режим наложения «Overlay» в Photoshop, а не режим наложения «Multiply», который искал исходный вопрос.
smileyborg
26

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

Я назвал свой класс, CSTintedImageViewи он наследуется от, UIViewпоскольку UIImageViewне вызывает drawRect:метод, как упоминалось в предыдущих ответах. Я установил назначенный инициализатор, похожий на тот, что есть в UIImageViewклассе.

Применение:

CSTintedImageView * imageView = [[CSTintedImageView alloc] initWithImage:[UIImage imageNamed:@"image"]];
imageView.tintColor = [UIColor redColor];

CSTintedImageView.h

@interface CSTintedImageView : UIView

@property (strong, nonatomic) UIImage * image;
@property (strong, nonatomic) UIColor * tintColor;

- (id)initWithImage:(UIImage *)image;

@end

CSTintedImageView.m

#import "CSTintedImageView.h"

@implementation CSTintedImageView

@synthesize image=_image;
@synthesize tintColor=_tintColor;

- (id)initWithImage:(UIImage *)image
{
    self = [super initWithFrame:CGRectMake(0, 0, image.size.width, image.size.height)];

    if(self)
    {
        self.image = image;

        //set the view to opaque
        self.opaque = NO;
    }

    return self;
}

- (void)setTintColor:(UIColor *)color
{
    _tintColor = color;

    //update every time the tint color is set
    [self setNeedsDisplay];
}

- (void)drawRect:(CGRect)rect
{
    CGContextRef context = UIGraphicsGetCurrentContext();    

    //resolve CG/iOS coordinate mismatch
    CGContextScaleCTM(context, 1, -1);
    CGContextTranslateCTM(context, 0, -rect.size.height);

    //set the clipping area to the image
    CGContextClipToMask(context, rect, _image.CGImage);

    //set the fill color
    CGContextSetFillColor(context, CGColorGetComponents(_tintColor.CGColor));
    CGContextFillRect(context, rect);    

    //blend mode overlay
    CGContextSetBlendMode(context, kCGBlendModeOverlay);

    //draw the image
    CGContextDrawImage(context, rect, _image.CGImage);    
}

@end
Stunner
источник
2
Спасибо. Это было первое решение в этом вопросе, которое действительно правильно поддерживало прозрачность изображения.
theTRON
Когда я пытаюсь использовать этот ответ, мой фон (область, которая должна быть прозрачной) становится черным. Вы знаете, что я здесь делаю не так?
livingtech 06
Ничего! (У меня был установлен цвет фона в раскадровке! Ооо!) Это потрясающее решение !!!
livingtech 06
2
Это решение работает хорошо, хотя я добился лучших результатов, сначала отрисовав изображение, а затем закрасив цвет сверху с помощью kCGBlendModeColor.
Джереми Фуллер
Метод drawRect: действительно является «мясом» этого решения. Остальное - дело вкуса / стиля. Благодарность! Сработало у меня!
Horsehoe7
26

Просто краткое пояснение (после некоторых исследований по этой теме). В Apple док здесь ясно говорится , что:

UIImageViewКласс оптимизирован , чтобы привлечь его изображения на экране. UIImageViewне вызывает drawRect:метод своих подклассов. Если ваш подкласс должен включать пользовательский код рисования, вы должны UIViewвместо этого подклассифицировать класс.

так что даже не тратьте время на попытки переопределить этот метод в UIImageViewподклассе. UIViewВместо этого начните с .

матторб
источник
9
О, как бы я хотел знать это шесть месяцев назад. Они должны поместить предупреждение шрифтом 72 пунктов, красным с меткой мигания.
willc2
Я знаю, ты имеешь в виду ... Я потерял полдня, возясь с этим.
mattorb 01
спасибо @mpstx ... Также поместите эту строку документации в ответ в кавычках ... Apple должна была сделать то же самое в документации ... :-)
Кришнабхадра 08
5

Это может быть очень полезно: PhotoshopFramework - одна из мощных библиотек для управления изображениями на Objective-C. Он был разработан для обеспечения тех же функций, которые знакомы пользователям Adobe Photoshop. Примеры: установка цветов с использованием RGB 0-255, применение фильтров наложения, преобразования ...

Это открытый исходный код, вот ссылка на проект: https://sourceforge.net/projects/photoshopframew/

Команда разработчиков SEQOY
источник
2
Выглядит интересно, но на что вы собираетесь сменить название, когда об этом узнают юристы Adobe?
Кристофер Джонсон,
1
Разберемся позже. Все равно не коммерческий продукт, это какое-то «кодовое имя». Также есть внутренняя библиотека.
Команда разработчиков SEQOY,
+1 за предложение библиотеки (даже если это небольшая самореклама) и за отсутствие шагов по изобретению велосипеда.
Тим
4
UIImage * image = mySourceImage;
UIColor * color = [UIColor yellowColor];  
UIGraphicsBeginImageContext(image.size);
[image drawInRect:CGRectMake(0, 0, image.size.width, image.size.height) blendMode:kCGBlendModeNormal alpha:1];
UIBezierPath * path = [UIBezierPath bezierPathWithRect:CGRectMake(0, 0, image.size.width, image.size.height)];
[color setFill];
[path fillWithBlendMode:kCGBlendModeMultiply alpha:1]; //look up blending modes for your needs
UIImage * newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
//use newImage for something
gngrwzrd
источник
Мне нужно было подкрасить UIImage, но не для использования его внутри UIImageView, а в качестве фонового изображения для UINavigationBar, и ваш код помог. Спасибо.
ncerezo
3

Вот еще один способ применить тонирование изображения, особенно если вы уже используете QuartzCore для чего-то другого. Это был мой ответ на аналогичный вопрос .

Импортировать QuartzCore:

#import <QuartzCore/QuartzCore.h>

Создайте прозрачный CALayer и добавьте его как подслой для изображения, которое вы хотите тонировать:

CALayer *sublayer = [CALayer layer];
[sublayer setBackgroundColor:[UIColor whiteColor].CGColor];
[sublayer setOpacity:0.3];
[sublayer setFrame:toBeTintedImage.frame];
[toBeTintedImage.layer addSublayer:sublayer];

Добавьте QuartzCore в список ваших проектов Framework (если его еще нет), иначе вы получите такие ошибки компилятора:

Undefined symbols for architecture i386: "_OBJC_CLASS_$_CALayer"
Lekksi
источник
2

Тем из вас, кто пытается создать подкласс класса UIImageView и застрять на «drawRect: не вызывается», обратите внимание, что вместо этого вы должны создать подкласс класса UIView, потому что для классов UIImageView метод «drawRect:» не вызывается. Подробнее здесь: drawRect не вызывается в моем подклассе UIImageView

Пьер
источник
0

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

Например:

UIImageView *yourPicture = (however you grab the image);
UIView *colorBlock = [[UIView alloc] initWithFrame:yourPicture.frame];
//Replace R G B and A with values from 0 - 1 based on your color and transparency
colorBlock.backgroundColor = [UIColor colorWithRed:R green:G blue:B alpha:A];
[yourPicture addSubView:colorBlock];

Документация для UIColor:

colorWithRed:green:blue:alpha:

Creates and returns a color object using the specified opacity and RGB component values.

+ (UIColor *)colorWithRed:(CGFloat)red green:(CGFloat)green blue:(CGFloat)blue alpha:(CGFloat)alpha

Parameters

red    - The red component of the color object, specified as a value from 0.0 to 1.0.

green  - The green component of the color object, specified as a value from 0.0 to 1.0.

blue   - The blue component of the color object, specified as a value from 0.0 to 1.0.



alpha  - The opacity value of the color object, specified as a value from 0.0 to 1.0.

Return Value

The color object. The color information represented by this object is in the device RGB colorspace. 
Тим Боуэн
источник
Core Graphics, похоже, может применять режимы гибки. Как, вот в чем вопрос.
willc2
0

Также вы можете рассмотреть возможность кэширования составного изображения для повышения производительности и просто его рендеринга в drawRect :, а затем обновить его, если грязный флаг действительно грязный. Хотя вы можете часто менять его, могут быть случаи, когда появляются отрисовки, и вы не испачкаетесь, поэтому вы можете просто обновить его из кеша. Если память важнее производительности, вы можете игнорировать это :)

Стив Риггинс
источник
Это то, что базовый CALayer делает за вас автоматически. Нет необходимости вручную кэшировать чертеж вида.
Деннис Манси,
0

У меня есть библиотека с открытым исходным кодом для этого: ios-image-filters

Серебряный
источник
0

Для Swift 2.0,

    let image: UIImage! = UIGraphicsGetImageFromCurrentImageContext()

    imgView.image = imgView.image!.imageWithRenderingMode(UIImageRenderingMode.AlwaysTemplate)

    imgView.tintColor = UIColor(red: 51/255.0, green: 51/255.0, blue: 

51/255.0, alpha: 1.0)
яж
источник
0

Я сделал для этого макросы:

#define removeTint(view) \
if ([((NSNumber *)[view.layer valueForKey:@"__hasTint"]) boolValue]) {\
for (CALayer *layer in [view.layer sublayers]) {\
if ([((NSNumber *)[layer valueForKey:@"__isTintLayer"]) boolValue]) {\
[layer removeFromSuperlayer];\
break;\
}\
}\
}

#define setTint(view, tintColor) \
{\
if ([((NSNumber *)[view.layer valueForKey:@"__hasTint"]) boolValue]) {\
removeTint(view);\
}\
[view.layer setValue:@(YES) forKey:@"__hasTint"];\
CALayer *tintLayer = [CALayer new];\
tintLayer.frame = view.bounds;\
tintLayer.backgroundColor = [tintColor CGColor];\
[tintLayer setValue:@(YES) forKey:@"__isTintLayer"];\
[view.layer addSublayer:tintLayer];\
}

Чтобы использовать, просто позвоните:

setTint(yourView, yourUIColor);
//Note: include opacity of tint in your UIColor using the alpha channel (RGBA), e.g. [UIColor colorWithRed:0.5f green:0.0 blue:0.0 alpha:0.25f];

При удалении оттенка просто позвоните:

removeTint(yourView);
Альберт Реншоу
источник