Bagaimana cara mewarnai gambar secara terprogram di iOS?

87

Saya ingin mewarnai gambar dengan referensi warna. Hasilnya akan terlihat seperti mode pencampuran Multiply di Photoshop, di mana warna putih akan diganti dengan warna :

teks alt

Saya akan mengubah nilai warna terus menerus.

Tindak lanjut: Saya akan meletakkan kode untuk melakukan ini di metode drawRect: ImageView saya, bukan?

Seperti biasa, potongan kode akan sangat membantu pemahaman saya, bukan tautan.

Pembaruan: Subclassing UIImageView dengan kode yang disarankan Ramin .

Saya meletakkan ini di viewDidLoad: dari pengontrol tampilan saya:

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

Saya melihat gambarnya, tetapi gambar itu tidak diwarnai. Saya juga mencoba memuat gambar lain, mengatur gambar di IB, dan memanggil setNeedsDisplay: di pengontrol tampilan saya.

Pembaruan : drawRect: tidak dipanggil.

Pembaruan terakhir: Saya menemukan proyek lama yang memiliki imageView diatur dengan benar sehingga saya dapat menguji kode Ramin dan bekerja seperti pesona!

Pembaruan terakhir, pembaruan terakhir:

Bagi Anda yang baru belajar tentang Core Graphics, berikut adalah hal paling sederhana yang mungkin bisa berfungsi.

Di subclass UIView Anda:

- (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
sumber
Saya menambahkan cuplikan sebelum memposting ke beberapa kode drawRect lama yang telah saya duduki dan berfungsi dengan baik. Mungkin ingin mencobanya tanpa panggilan [super viewDidLoad] (atau pindahkan ke atas). Juga periksa ulang untuk memastikan siapa pun yang mengalokasikan objek ini mengalokasikan versi turunannya bukan vanilla UIImageView (yaitu jika dimuat di ujung pena, pengalokasi, dll).
Ramin
Saran lain jika hal di atas tidak berhasil: daripada membuat subclass UIImageView menjadi UIView biasa dan menambahkan overlayColor dan properti UIImage yang disebut 'image' yang dapat Anda setel. Kemudian letakkan drawRect di sana. Kode drawRect tidak peduli apakah nilai 'image' berasal dari UIImageView atau dari properti yang Anda tentukan.
Ramin
Bukankah ini akan gagal jika gambar dengan alfa? Bagaimana jika saya hanya ingin mewarnai bagian gambar yang digambar saja? (Sama seperti UIButton?)
Sunil Chauhan

Jawaban:

45

Pertama, Anda ingin membuat subkelas UIImageView dan mengganti metode drawRect. Kelas Anda membutuhkan properti UIColor (sebut saja overlayColor) untuk menahan warna campuran dan penyetel kustom yang memaksa menggambar ulang saat warna berubah. Sesuatu seperti ini:

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

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

Dalam metode drawRect Anda ingin menggambar gambar terlebih dahulu kemudian melapisinya dengan persegi panjang yang diisi dengan warna yang Anda inginkan bersama dengan mode pencampuran yang tepat, seperti ini:

- (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);
}

Biasanya untuk mengoptimalkan gambar, Anda akan membatasi gambar sebenarnya hanya ke area yang diteruskan ke drawRect, tetapi karena gambar latar belakang harus digambar ulang setiap kali warna berubah, sepertinya semuanya perlu disegarkan.

Untuk menggunakannya, buat instance objek lalu setel imageproperti (diwarisi dari UIImageView) ke gambar dan overlayColorke nilai UIColor (tingkat campuran dapat disesuaikan dengan mengubah nilai alpha dari warna yang Anda turunkan).

Ramin
sumber
Saran tindak lanjut terlampir pada permintaan asli (di atas).
Ramin
harus menambahkan CGContextSaveGState (konteks); sebelum mengubah mode campuran atau Anda mendapatkan gstack underflow.
Willc2
D'oh, terima kasih. Kesalahan salin / tempel. Saya telah memperbaikinya di cuplikan kode.
Ramin
10
(Seperti yang juga dinyatakan dalam jawaban lain di sini) Pertimbangan Khusus Kelas UIImageView dioptimalkan untuk menarik gambarnya ke tampilan. UIImageView tidak akan memanggil drawRect: subclass. Jika subclass Anda memerlukan kode gambar kustom, Anda disarankan untuk menggunakan UIView sebagai kelas dasar. Ini berarti Anda tidak bisa membuat subclass UIImageView dan mengharapkan drawRect dipanggil sama sekali .
Jonny
Hai, sayangnya saya mencoba kodenya dan tidak berhasil: /. Lihat jawaban di bawah oleh @mpstx, yang mengatakan drawRect tidak akan dipanggil dari UIImageView dan sebagai gantinya, Anda harus menggunakan UIView (dengan UIImage) yang berfungsi dengan baik untuk saya.
jklp
77

Di iOS7, mereka telah memperkenalkan properti tintColor di UIImageView dan renderingMode di UIImage. Untuk mewarnai UIImage di iOS7, yang harus Anda lakukan adalah:

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
Toland Hon
sumber
18
Ini jelas merupakan cara terbaik dan termudah untuk menggunakan iOS 7 jika ini adalah perilaku tint yang Anda cari. Tapi kode ini menerapkan warna seolah-olah Anda menggunakan blending mode "Overlay" 100% buram di Photoshop, bukan blending mode "Multiply" yang dicari oleh pertanyaan asli.
smileyborg
26

Saya ingin mewarnai gambar dengan alfa dan saya membuat kelas berikut. Tolong beri tahu saya jika Anda menemukan masalah dengan itu.

Saya telah menamai kelas saya CSTintedImageViewdan itu mewarisi UIViewsejak UIImageViewtidak memanggil drawRect:metode, seperti yang disebutkan dalam balasan sebelumnya. Saya telah menetapkan penginisialisasi yang ditunjuk mirip dengan yang ditemukan di UIImageViewkelas.

Pemakaian:

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
Cantik
sumber
2
Terima kasih. Ini adalah solusi pertama dalam pertanyaan ini yang benar-benar mendukung transparansi gambar dengan baik.
theTRON
Ketika saya mencoba menggunakan jawaban ini, latar belakang saya (area yang seharusnya transparan) adalah hitam. Apakah Anda tahu apa yang bisa saya lakukan salah di sini?
livingtech
Sudahlah! (Saya memiliki set warna latar belakang di storyboard! D'oh!) Solusi ini luar biasa !!!
livingtech
2
Solusi ini berfungsi dengan baik, meskipun saya mendapatkan hasil yang lebih baik dengan merender gambar terlebih dahulu, kemudian mengisi warna di atasnya dengan kCGBlendModeColor.
Jeremy Fuller
metode drawRect: benar-benar "inti" dari solusi ini. Selebihnya adalah soal selera / gaya. Terima kasih! Bekerja untuk saya!
tapal
26

Hanya klarifikasi singkat (setelah beberapa penelitian tentang topik ini). Dokumen Apple di sini dengan jelas menyatakan bahwa:

The UIImageViewkelas dioptimalkan untuk menarik gambar ke layar. UIImageViewtidak memanggil drawRect:metode subkelasnya. Jika subclass Anda perlu menyertakan kode gambar kustom, Anda harus membuat subclass UIViewkelas tersebut.

jadi jangan buang waktu untuk mencoba menimpa metode itu dalam UIImageViewsubclass. Mulailah dengan UIViewsebagai gantinya.

mattorb
sumber
9
Oh, betapa aku berharap aku tahu ini enam bulan lalu. Mereka harus memberi peringatan dalam tipe 72 poin, merah dengan tag kedip.
willc2
Aku tahu maksudmu ... Aku kehilangan setengah hari mengotak-atiknya.
mattorb
terima kasih @mpstx ... Letakkan juga baris dokumentasi di jawaban dalam tanda kutip ... Apple seharusnya melakukan hal yang sama dalam dokumentasi ... :-)
Krishnabhadra
5

Ini bisa sangat berguna: PhotoshopFramework adalah salah satu pustaka yang ampuh untuk memanipulasi gambar di Objective-C. Ini dikembangkan untuk menghadirkan fungsionalitas yang sama dengan yang dikenal oleh pengguna Adobe Photoshop. Contoh: Atur warna menggunakan RGB 0-255, terapkan filter campuran, transformasi ...

Apakah open source, ini tautan proyek: https://sourceforge.net/projects/photoshopframew/

Tim Pengembang SEQOY
sumber
2
Kelihatannya menarik, tapi apa yang akan Anda ubah namanya menjadi ketika pengacara Adobe mengetahuinya?
Kristopher Johnson
1
Kami akan mencari tahu nanti. Bukan produk komersial pula, adalah semacam "codename". Juga merupakan perpustakaan internal.
Tim Pengembang SEQOY
+1 untuk menyarankan perpustakaan (meskipun itu adalah promosi diri kecil), dan tidak memberikan langkah-langkah untuk menemukan kembali roda.
Tim
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.dll
sumber
Saya perlu mewarnai UIImage, tetapi tidak menggunakannya di dalam UIImageView, tetapi sebagai gambar latar belakang untuk UINavigationBar, dan kode Anda berhasil. Terima kasih.
ncerezo
3

Berikut adalah cara lain untuk menerapkan pewarnaan gambar, terutama jika Anda sudah menggunakan QuartzCore untuk hal lain. Ini adalah jawaban saya untuk pertanyaan serupa .

Impor QuartzCore:

#import <QuartzCore/QuartzCore.h>

Buat CALayer transparan dan tambahkan sebagai sublayer untuk gambar yang ingin Anda warnai:

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

Tambahkan QuartzCore ke daftar Kerangka proyek Anda (jika belum ada), jika tidak, Anda akan mendapatkan kesalahan kompiler seperti ini:

Undefined symbols for architecture i386: "_OBJC_CLASS_$_CALayer"
lekksi
sumber
2

Bagi Anda yang mencoba membuat subkelas kelas UIImageView dan terjebak di "drawRect: tidak dipanggil", perhatikan bahwa Anda harus membuat subkelas kelas UIView sebagai gantinya, karena untuk kelas UIImageView, metode "drawRect:" tidak dipanggil. Baca lebih lanjut di sini: drawRect tidak dipanggil di subclass UIImageView saya

pierre
sumber
0

Satu-satunya hal yang dapat saya pikirkan adalah membuat tampilan persegi panjang yang sebagian besar transparan dengan warna yang diinginkan dan meletakkannya di atas tampilan gambar Anda dengan menambahkannya sebagai subview. Saya tidak yakin apakah ini benar-benar akan mewarnai gambar seperti yang Anda bayangkan, saya tidak yakin bagaimana Anda akan meretas gambar dan secara selektif mengganti warna tertentu dengan yang lain ... kedengarannya cukup ambisius bagi saya.

Sebagai contoh:

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];

Dokumentasi untuk 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. 
Tim Bowen
sumber
Core Graphics sepertinya bisa mengaplikasikan mode bending. Bagaimana, pertanyaannya.
Willc2
0

Anda juga mungkin ingin mempertimbangkan untuk menyimpan gambar gabungan untuk kinerja dan hanya merendernya di drawRect :, lalu memperbaruinya jika bendera kotor memang kotor. Meskipun Anda mungkin sering mengubahnya, mungkin ada kasus di mana hasil imbang masuk dan Anda tidak kotor, jadi Anda dapat menyegarkan dari cache. Jika memori lebih merupakan masalah daripada kinerja, Anda dapat mengabaikan ini :)

Steve Riggins
sumber
Itulah yang dilakukan oleh CALayer yang mendasari untuk Anda secara otomatis. Tidak perlu menyimpan gambar tampilan secara manual.
Dennis Munsie
0

Untuk 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)
yazh
sumber
0

Saya membuat makro untuk tujuan ini:

#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];\
}

Untuk menggunakan, cukup panggil:

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];

Saat menghapus tint cukup panggil:

removeTint(yourView);
Albert Renshaw
sumber