Як би я програмував зображення програмно на iOS?


87

Я хотів би підфарбувати зображення кольоровим посиланням. Результати повинні виглядати як режим багаторазового змішування у Photoshop, де білі кольори замінюватимуться відтінком :

текст заміщення

Я постійно змінюватиму значення кольору.

Подальші дії: я б поставив код для цього в моєму методі drawRect: ImageView, так?

Як завжди, фрагмент коду дуже допоміг би мені зрозуміти, на відміну від посилання.

Оновлення: підкласи UIImageView з кодом Ramin запропонував.

Я помістив це у 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
}

Я додав фрагмент перед публікацією до якогось старого коду drawRect, який я сидів, і він працював нормально. Можливо, захочеться спробувати без виклику [super viewDidLoad] (або перемістіть його вище). Також перевірте, переконайтеся, що той, хто виділяє цей об’єкт, виділяє похідну версію, а не ванільний UIImageView (тобто, якщо він завантажений у перо, розподільник тощо).
Рамін

Ще одна порада, якщо вищезазначене не працює: замість підкласу UIImageView підкласуйте звичайний UIView та додайте overlayColor та властивість UIImage під назвою 'image', яке ви можете встановити. Потім помістіть туди drawRect. Коду drawRect байдуже, значення "зображення" походить від 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 (контекст); перед зміною режиму змішування або ви отримаєте недоповнення gstack.
willc2

О, дякую. Помилка копіювання / вставки. Я виправив це у фрагменті коду.
Рамін,

10
(Як також зазначено в іншій відповіді тут) Особливі міркування Клас UIImageView оптимізований для виведення своїх зображень на дисплей. UIImageView не буде викликати drawRect: підклас. Якщо ваш підклас потребує власного коду малювання, рекомендується використовувати UIView як базовий клас. Це означає , що ви не можете створити підклас UIImageView і планують DrawRect називати, взагалі .
Джонні,

Привіт, на жаль, я спробував код, і він не спрацював: /. Дивіться відповідь нижче @mpstx, який говорить, що drawRect не буде викликатись з UIImageView, і замість цього ви повинні використовувати UIView (з UIImage), який у мене справно працював.
jklp

77

У iOS7 вони ввели властивість tintColor на UIImageView та renderingMode на 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

2
Дякую. Це було перше рішення у цьому питанні, яке насправді належним чином підтримувало прозорість зображення.
ТРОН

Коли я намагаюся використати цю відповідь, мій фон (область, яка повинна бути прозорою) замість цього чорний. Ти знаєш, що я тут міг зробити неправильно?
livingtech

Не зважай! (У мене в розкадровці встановлено колір тла! О, о!) Це рішення чудове !!!
livingtech

2
Це рішення працює добре, хоча я отримав кращі результати, спершу відтворивши зображення, а потім заповнивши колір зверху kCGBlendModeColor.
Джеремі Фуллер,

метод drawRect: насправді є «м’ясом» цього рішення. Решта - справа смаку / стилю. Дякую! Працював у мене!
підкова7

26

Просто коротке роз’яснення (після деяких досліджень на цю тему). У документі Apple тут чітко зазначено, що:

UIImageViewКлас оптимізований , щоб привернути його зображення на екрані. UIImageViewне викликає drawRect:метод своїх підкласів. Якщо ваш підклас повинен містити власний код малювання, UIViewзамість цього слід підкласувати клас.

тому навіть не витрачайте часу, намагаючись замінити цей метод у UIImageViewпідкласі. Почніть із UIViewцього.


9
О, як би я хотів знати це півроку тому. Вони повинні помістити попередження в 72-крапковий шрифт, червоний з блиманням.
willc2

Я знаю, ти маєш на увазі ... Я втратив півдня, возившись із цим.
mattorb

спасибі @mpstx ... Також поставте цей рядок документації у відповідь у лапках ... Apple повинна була зробити те саме в документації ... :-)
Крішнабхадра

5

Це може бути дуже корисно: PhotoshopFramework - це одна потужна бібліотека для обробки зображень на Objective-C. Він був розроблений, щоб забезпечити ті самі функції, які знайомі користувачам Adobe Photoshop. Приклади: Встановіть кольори за допомогою RGB 0-255, застосуйте комбіновані файлери, перетворення ...

Є відкритим кодом, ось посилання на проект: https://sourceforge.net/projects/photoshopframew/


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

Мені потрібно було підфарбувати 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"

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. 

Основна графіка, здається, може застосовувати режими згинання. Як, це питання.
willc2

0

Також ви можете розглянути можливість кешування складеного зображення для продуктивності та просто зробити його в drawRect :, а потім оновити його, якщо брудний прапор справді брудний. Хоча ви можете часто його міняти, трапляються випадки, коли надходять розіграші, і ви не забруднені, тому ви можете просто оновити кеш. Якщо пам’ять є більшою проблемою, ніж продуктивністю, ви можете проігнорувати це :)


Це те, що основний CALayer робить для вас автоматично. Не потрібно кешувати креслення подання вручну.
Dennis Munsie


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