// 1. CGImageRef inputCGImage = [image CGImage]; NSUInteger width = CGImageGetWidth(inputCGImage); NSUInteger height = CGImageGetHeight(inputCGImage); // 2. NSUInteger bytesPerPixel = 4; NSUInteger bytesPerRow = bytesPerPixel * width; NSUInteger bitsPerComponent = 8; UInt32 * pixels; pixels = (UInt32 *) calloc(height * width, sizeof(UInt32)); // 3. CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); CGContextRef context = CGBitmapContextCreate(pixels, width, height, bitsPerComponent, bytesPerRow, colorSpace, kCGImageAlphaPRemultipliedLast | kCGBitmapByteOrder32Big); // 4. CGContextDrawImage(context, CGRectMake(0, 0, width, height), inputCGImage); // 5. Cleanup CGColorSpaceRelease(colorSpace); CGContextRelease(context); 現在,讓我們分段的來看一下: 1:第一部分:把UIImage對象轉換為需要被核心圖形庫調用的CGImage對象。同時,得到圖形的寬度和高度。 2:第二部分:由于你使用的是32位RGB顏色空間模式,你需要定義一些參數bytesPerPixel(每像素大小)和bitsPerComponent(每個顏色通道大?。?,然后計算圖像bytesPerRow(每行有大)。最后,使用一個數組來存儲像素的值。 3:第三部分:創建一個RGB模式的顏色空間CGColorSpace和一個容器CGBitmapContext,將像素指針參數傳遞到容器中緩存進行存儲。在后面的章節中將會進一步研究核圖形庫。 4:第四部分:把緩存中的圖形繪制到顯示器上。像素的填充格式是由你在創建context的時候進行指定的。 5:第五部分:清除colorSpace和context. NOTE:當你繪制圖像的時候,設備的GPU會進行解碼并將它顯示在屏幕。為了訪問本地數據,你需要一份像素的復制,就像剛才做的那樣。 此時此刻,pixels存儲著圖像的所有像素信息。下面的幾行代碼會對pixels進行遍歷,并打印:// 1. #define Mask8(x) ( (x) & 0xFF ) #define R(x) ( Mask8(x) ) #define G(x) ( Mask8(x >> 8 ) ) #define B(x) ( Mask8(x >> 16) ) NSLog(@"Brightness of image:"); // 2. UInt32 * currentPixel = pixels; for (NSUInteger j = 0; j < height; j++) { for (NSUInteger i = 0; i < width; i++) { // 3. UInt32 color = *currentPixel; printf("%3.0f ", (R(color)+G(color)+B(color))/3.0); // 4. currentPixel++; } printf("/n"); } 代碼解釋: 1:定義了一些簡單處理32位像素的宏。為了得到紅色通道的值,你需要得到前8位。為了得到其它的顏色通道值,你需要進行位移并取截取。 2:定義一個指向第一個像素的指針,并使用2個for循環來遍歷像素。其實也可以使用一個for循環從0遍歷到width*height,但是這樣寫更容易理解圖形是二維的。 3:得到當前像素的值賦值給currentPixel并把它的亮度值打印出來。 4:增加currentPixel的值,使它指向下一個像素。如果你對指針的運算比較生疏,記住這個:currentPixel是一個指向UInt32的變量,當你把它加1后,它就會向前移動4字節(32位),然后指向了下一個像素的值。 提示:還有一種非正統的方法就是把currentPiexl聲明為一個指向8字節的類型的指針,比如char。這種方法,你每增加1,你將會移動圖形的下一個顏色通道。與它進行位移運算,你會得到顏色通道的8位數值。 此時此刻,這個程序只是打印出了原圖的像素信息,但并沒有進行任何修改!下面將會教你如何進行修改。 SpookCame-原圖修改四種研究方法都會在本小節進行,你將會花費更多的時間在本節,因為它包括了圖形圖像處理的第一原則。掌握了這個方法你會明白其它庫所做的。 在本方法中,你會遍歷每一個像素,就像之前做的那個,但這次,將會對每個像素進行新的賦值。 這種方法的優點是容易實現和理解;缺點就是掃描大的圖形和效果的時候會更復雜,不精簡。 正如你在程序開始看到的,ImageProcessor類已經存在。將它應用到ViewController中,替換-setupWithImage,代碼如下:- (void)setupWithImage:(UIImage*)image { UIImage * fixedImage = [image imageWithFixedOrientation]; self.workingImage = fixedImage; // Commence with processing! [ImageProcessor sharedProcessor].delegate = self; [[ImageProcessor sharedProcessor] processImage:fixedImage]; } 注釋掉 -viewDidLoad 中下面的代碼:// [self setupWithImage:[UIImage imageNamed:@"Ghost_tiny.png"]]; 現在,打開 ImageProcessor.m。如你所見,ImageProcessor 是單例模式,調用 -processUsingPixels 來加載圖像,然后通過 ImageProcessorDelegate 返回輸出。 -processsUsingPixels:是之前你所看到獲得圖形像素代碼的一種復制品,如同inputImage。注意兩個額外的宏A(x)和RGBAMake(r,g,b,a)的定義,用來方便處理。 編譯,并運行。從相冊(拍照)選擇一張圖片,它將會出現在屏幕上:照片中的人看上去在放松,是時候把幽靈放進去了! 在processUsingPixels的返回語句前,添加如下代碼,創建一個幽靈的CGImageRef對象。UIImage * ghostImage = [UIImage imageNamed:@"ghost"];CGImageRef ghostCGImage = [ghostImage CGImage]; 現在,做一些數學運算來確定幽靈圖像放在原圖的什么位置。
CGFloat ghostImageaspectRatio = ghostImage.size.width / ghostImage.size.height; NSInteger targetGhostWidth = inputWidth * 0.25; CGSize ghostSize = CGSizeMake(targetGhostWidth, targetGhostWidth / ghostImageAspectRatio); CGPoint ghostOrigin = CGPointMake(inputWidth * 0.5, inputHeight * 0.2); 以上代碼會把幽靈的圖像寬度縮小25%,并把它的原點設定在點ghostOrigin。 下一步是創建一張幽靈圖像的緩存圖,NSUInteger ghostBytesPerRow = bytesPerPixel * ghostSize.width; UInt32 * ghostPixels = (UInt32 *)calloc(ghostSize.width * ghostSize.height, sizeof(UInt32)); CGContextRef ghostContext = CGBitmapContextCreate(ghostPixels, ghostSize.width, ghostSize.height, bit sPerComponent, ghostBytesPerRow, colorSpace, kCG ImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big); CGContextDrawImage(ghostContext, CGRectMake(0, 0, ghostSize.width, ghostSize.height),ghostCGImage); 上面的代碼和你從inputImage中獲得像素信息一樣。不同的地方是,圖像會被縮小尺寸,變得更小了。 現在已經到了把幽靈圖像合并到你的照片中的最佳時間了。 合并:像前面提到的,每一個顏色都有一個透明通道來標識透明度。并且,你每創建一張圖像,每一個像素都會有一個顏色值。 所以,如果遇到有透明度和半透明的顏色值該如何處理呢? 答案是,對透明度進行混合。在最頂層的顏色會使用一個公式與它后面的顏色進行混合。公式如下:NewColor = TopColor * TopColor.Alpha + BottomColor * (1 - TopColor.Alpha) 這是一個標準的線性差值方程。 ·當頂層透明度為1時,新的顏色值等于頂層顏色值?!ぎ旐攲油该鞫葹?時,新的顏色值于底層顏色值?!ぷ詈?,當頂層的透明度值是0到1之前的時候,新的顏色值會混合借于頂層和底層顏色值之間。 還可以用 premultiplied alpha的方法。 當處理成千上萬像素的時候,他的性能會得以發揮。 好,回到幽靈圖。 如同其它位圖運算一樣,你需要一些循環來遍歷每一個像素。但是,你只需要遍歷那些你需要修改的像素。 把下面的代碼添加到processUsingPixels的下面,還是放在返回語句的前面:NSUInteger offsetPixelCountForInput = ghostOrigin.y * inputWidth + ghostOrigin.x; for (NSUInteger j = 0; j < ghostSize.height; j++) { for (NSUInteger i = 0; i < ghostSize.width; i++) { UInt32 * inputPixel = inputPixels + j * inputWidth + i + offsetPixelCountForInput; UInt32 inputColor = *inputPixel; UInt32 * ghostPixel = ghostPixels + j * (int)ghostSize.width + i; UInt32 ghostColor = *ghostPixel; // Do some processing here } } 通過對幽靈圖像像素數的循環和offsetPixelCountForInput獲得輸入的圖像。記住,雖然你使用的是2維數據存儲圖像,但在內存他它實際上是一維的。 下一步,添加下面的代碼到注釋語句 Do some processing here的下面來進行混合:// Blend the ghost with 50% alpha CGFloat ghostAlpha = 0.5f * (A(ghostColor) / 255.0); UInt32 newR = R(inputColor) * (1 - ghostAlpha) + R(ghostColor) * ghostAlpha; UInt32 newG = G(inputColor) * (1 - ghostAlpha) + G(ghostColor) * ghostAlpha; UInt32 newB = B(inputColor) * (1 - ghostAlpha) + B(ghostColor) * ghostAlpha; // Clamp, not really useful here :p newR = MAX(0,MIN(255, newR)); newG = MAX(0,MIN(255, newG)); newB = MAX(0,MIN(255, newB)); *inputPixel = RGBAMake(newR, newG, newB, A(inputColor)); 這部分有2點需要說明: 1:你將幽靈圖像的每一個像素的透明通道都乘以了0.5,使它成為半透明狀態。然后將它混合到圖像中像之前討論的那樣。 2:clamping部分將每個顏色的值范圍進行限定到0到255之間,雖然一般情況下值不會越界。但是,大多數情況下需要進行這種限定防止發生意外的錯誤輸出。 最后一步,添加下面的代碼到 processUsingPixels 的下面,替換之前的返回語句:// Create a new UIImage CGImageRef newCGImage = CGBitmapContextCreateImage(context); UIImage * processedImage = [UIImage imageWithCGImage:newCGImage]; return processedImage; 上面的代碼創建了一張新的UIImage并返回它。暫時忽視掉內存泄露問題。編譯并運行,你將會看到漂浮的幽靈圖像:好了,完成了,這個程序簡直就像個病毒! 黑白顏色最后一種效果。嘗試自己實現黑白顏色效果。為了做到這點,你需要把每一個像素的紅色,綠色,藍色通道的值設定成三個通道原始顏色值的平均值,就像開始的時候輸出幽靈圖像所有像素亮度值那樣。 在注釋語句// create a new UIImage前添加上一步的代碼 。 找到了嗎?
// Convert the image to black and white for (NSUInteger j = 0; j < inputHeight; j++) { for (NSUInteger i = 0; i < inputWidth; i++) { UInt32 * currentPixel = inputPixels + (j * inputWidth) + i; UInt32 color = *currentPixel; // Average of RGB = greyscale UInt32 averageColor = (R(color) + G(color) + B(color)) / 3.0; *currentPixel = RGBAMake(averageColor, averageColor, averageColor, A(color)); } } 最后的一步就是清除內存。ARC不能代替你對CGImageRefs和CGContexts進行管理。添加如下代碼到返回語句之前。CGColorSpaceRelease(colorSpace); CGContextRelease(context); CGContextRelease(ghostContext); free(inputPixels); free(ghostPixels); 編譯并運行,不要被結果嚇到:
新聞熱點
疑難解答