鍍金池/ 教程/ iOS/ 圖片格式
與四軸無人機(jī)的通訊
在沙盒中編寫腳本
結(jié)構(gòu)體和值類型
深入理解 CocoaPods
UICollectionView + UIKit 力學(xué)
NSString 與 Unicode
代碼簽名探析
測(cè)試
架構(gòu)
第二期-并發(fā)編程
Metal
自定義控件
iOS 中的行為
行為驅(qū)動(dòng)開發(fā)
Collection View 動(dòng)畫
截圖測(cè)試
MVVM 介紹
使 Mac 應(yīng)用數(shù)據(jù)腳本化
一個(gè)完整的 Core Data 應(yīng)用
插件
字符串
為 iOS 建立 Travis CI
先進(jìn)的自動(dòng)布局工具箱
動(dòng)畫
為 iOS 7 重新設(shè)計(jì) App
XPC
從 NSURLConnection 到 NSURLSession
Core Data 網(wǎng)絡(luò)應(yīng)用實(shí)例
GPU 加速下的圖像處理
自定義 Core Data 遷移
子類
與調(diào)試器共舞 - LLDB 的華爾茲
圖片格式
并發(fā)編程:API 及挑戰(zhàn)
IP,TCP 和 HTTP
動(dòng)畫解釋
響應(yīng)式 Android 應(yīng)用
初識(shí) TextKit
客戶端
View-Layer 協(xié)作
回到 Mac
Android
Core Image 介紹
自定義 Formatters
Scene Kit
調(diào)試
項(xiàng)目介紹
Swift 的強(qiáng)大之處
測(cè)試并發(fā)程序
Android 通知中心
調(diào)試:案例學(xué)習(xí)
從 UIKit 到 AppKit
iOS 7 : 隱藏技巧和變通之道
安全
底層并發(fā) API
消息傳遞機(jī)制
更輕量的 View Controllers
用 SQLite 和 FMDB 替代 Core Data
字符串解析
終身學(xué)習(xí)的一代人
視頻
Playground 快速原型制作
Omni 內(nèi)部
同步數(shù)據(jù)
設(shè)計(jì)優(yōu)雅的移動(dòng)游戲
繪制像素到屏幕上
相機(jī)與照片
音頻 API 一覽
交互式動(dòng)畫
常見的后臺(tái)實(shí)踐
糟糕的測(cè)試
避免濫用單例
數(shù)據(jù)模型和模型對(duì)象
Core Data
字符串本地化
View Controller 轉(zhuǎn)場(chǎng)
照片框架
響應(yīng)式視圖
Square Register 中的擴(kuò)張
DTrace
基礎(chǔ)集合類
視頻工具箱和硬件加速
字符串渲染
讓東西變得不那么糟
游戲中的多點(diǎn)互聯(lián)
iCloud 和 Core Data
Views
虛擬音域 - 聲音設(shè)計(jì)的藝術(shù)
導(dǎo)航應(yīng)用
線程安全類的設(shè)計(jì)
置換測(cè)試: Mock, Stub 和其他
Build 工具
KVC 和 KVO
Core Image 和視頻
Android Intents
在 iOS 上捕獲視頻
四軸無人機(jī)項(xiàng)目
Mach-O 可執(zhí)行文件
UI 測(cè)試
值對(duì)象
活動(dòng)追蹤
依賴注入
Swift
項(xiàng)目管理
整潔的 Table View 代碼
Swift 方法的多面性
為什么今天安全仍然重要
Core Data 概述
Foundation
Swift 的函數(shù)式 API
iOS 7 的多任務(wù)
自定義 Collection View 布局
測(cè)試 View Controllers
訪談
收據(jù)驗(yàn)證
數(shù)據(jù)同步
自定義 ViewController 容器轉(zhuǎn)場(chǎng)
游戲
調(diào)試核對(duì)清單
View Controller 容器
學(xué)無止境
XCTest 測(cè)試實(shí)戰(zhàn)
iOS 7
Layer 中自定義屬性的動(dòng)畫
第一期-更輕量的 View Controllers
精通 iCloud 文檔存儲(chǔ)
代碼審查的藝術(shù):Dropbox 的故事
GPU 加速下的圖像視覺
Artsy
照片擴(kuò)展
理解 Scroll Views
使用 VIPER 構(gòu)建 iOS 應(yīng)用
Android 中的 SQLite 數(shù)據(jù)庫(kù)支持
Fetch 請(qǐng)求
導(dǎo)入大數(shù)據(jù)集
iOS 開發(fā)者的 Android 第一課
iOS 上的相機(jī)捕捉
語(yǔ)言標(biāo)簽
同步案例學(xué)習(xí)
依賴注入和注解,為什么 Java 比你想象的要好
編譯器
基于 OpenCV 的人臉識(shí)別
玩轉(zhuǎn)字符串
相機(jī)工作原理
Build 過程

圖片格式

數(shù)據(jù)存儲(chǔ)

在計(jì)算機(jī)上存儲(chǔ)文本很容易。我們以字母或字符作為一個(gè)基本單元,建立了一個(gè)從數(shù)字到字符編碼的簡(jiǎn)單映射來進(jìn)行編碼。圖形信息則不同,有很多不同的方式來表示圖像,每種方式的優(yōu)點(diǎn)和缺點(diǎn)也都各有不同。

文本是線性且一維的。撇開文字方向的問題,我們只需要知道序列中的下一個(gè)元素是什么就可以有效的存儲(chǔ)文字。

圖像則更復(fù)雜。首先,它們是二維的,所以我們需要考慮如何表示圖像中某個(gè)特定位置的值。然后,我們需要考慮具體的值應(yīng)該如何量化。另外,根據(jù)我們捕捉圖像的途徑,也會(huì)有不同的方式來編碼圖形數(shù)據(jù)。一般來說,最直觀的方式是將其存為位圖數(shù)據(jù),可如果你想處理一組幾何圖形,效率就會(huì)偏低。一個(gè)圓形可以只由三個(gè)值 (兩個(gè)坐標(biāo)值和半徑) 來表示,使用位圖會(huì)使文件更大,卻只能做粗略的近似。

以上所說的問題向我們展示了不同圖像格式之間的第一個(gè)區(qū)別:位圖與矢量圖像。不同于位圖把值存在陣列中,矢量格式存儲(chǔ)的是繪圖圖像的指令。在處理一些可以被歸納為幾何形狀的簡(jiǎn)單圖像時(shí),這樣做顯然更有效率;但面對(duì)照片數(shù)據(jù)時(shí)矢量?jī)?chǔ)存就會(huì)顯得乏力了。建筑師設(shè)計(jì)房屋更傾向于使用矢量的方式,因?yàn)槭噶扛袷讲⒉粌H僅局限于線條的繪制,也可以用漸變或圖案的填充作為展示,所以利用矢量方式完全可以生成房屋的擬真渲染圖。

用于填充的圖案單元?jiǎng)t更適合被儲(chǔ)存為一個(gè)位圖,在這種情況下,我們可能需要一個(gè)混合格式。一個(gè)非常普遍的混合格式的一個(gè)例子是 PostScript,(或者時(shí)下比較流行的衍生格式,PDF),它基本上是一個(gè)用于繪制圖像的描述語(yǔ)言。上述格式主要針對(duì)印刷業(yè),而 NeXT 和 Adobe 開發(fā)的 Display Postscript 則是進(jìn)行屏幕繪制的指令集。PostScript 能夠排布字母,甚至位圖,這使得它成為了一個(gè)非常靈活的格式。

矢量圖像

矢量格式的一大優(yōu)點(diǎn)是縮放。矢量格式的圖像其實(shí)是一組繪圖指令,這些指令通常是獨(dú)立于尺寸的。如果你想擴(kuò)大一個(gè)圓形,只需在繪制前擴(kuò)大它的半徑就可以了。位圖則沒這么容易。最起碼,如果擴(kuò)大的比例不是二的倍數(shù),就會(huì)涉及到重繪圖像,并且各個(gè)元素都只是簡(jiǎn)單地增加尺寸,成為一個(gè)色塊。由于我們不知道這圖像是一個(gè)圓形,所以無法確保弧線的準(zhǔn)確描繪,效果看起來肯定不如按比例繪制的線條那樣好。也因此,在像素密度不同的設(shè)備中,矢量圖像作為圖形資源會(huì)非常有用。位圖的話,同樣的圖標(biāo),在視網(wǎng)膜屏幕之前的 iPhone 上看起來并沒有問題,在拉伸兩倍后的視網(wǎng)膜屏幕上看起來就會(huì)發(fā)虛。就好像僅適配了 iPhone 的 App 運(yùn)行在 iPad 的 2x 模式下就不再那么清晰了。

雖然 Xcode 6 已經(jīng)支持了 PDF 格式,但迄今仍不完善,只是在編譯時(shí)將其創(chuàng)建成了位圖圖像。最常見的矢量圖像格式為 SVG,在 iOS 中也有一個(gè)渲染 SVG 文件的庫(kù),SVGKit。

位圖

大部分圖像都是以位圖方式處理的,從這里開始,我們就將重點(diǎn)放在如何處理它們上。第一個(gè)問題,是如何表示兩個(gè)維度。所有的格式都以一系列連續(xù)的行作為單元,而每一行則水平地按順序存儲(chǔ)了每個(gè)像素。大多數(shù)格式會(huì)按照行的順序進(jìn)行存儲(chǔ),但是這并不絕對(duì),比如常見的交叉格式,就不嚴(yán)格按照行順序。其優(yōu)點(diǎn)是當(dāng)圖像被部分加載時(shí),可以更好的顯示預(yù)覽圖像。在互聯(lián)網(wǎng)初期,這是一個(gè)問題,隨著數(shù)據(jù)的傳輸速度提升,現(xiàn)在已經(jīng)不再被當(dāng)做重點(diǎn)。

表示位圖最簡(jiǎn)單的方法是將二進(jìn)制作為每個(gè)像素的值:一個(gè)像素只有開、關(guān)兩種狀態(tài),我們可以在一個(gè)字節(jié)中存儲(chǔ)八個(gè)像素,效率非常高。不過,由于每一位只有最多兩個(gè)值,我們只能儲(chǔ)存兩種顏色。考慮到現(xiàn)實(shí)中的顏色數(shù)以百萬計(jì),上述方法聽起來并不是很有用。不過有一種情況還是需要用到這樣的方法:遮罩。比如,圖像的遮罩可以被用于透明性,在 iOS 中,遮罩被應(yīng)用在 tab bar 的圖標(biāo)上 (即便實(shí)際圖標(biāo)不是單像素位圖)。

如果要添加更多的顏色,有兩個(gè)基本的選擇:使用一個(gè)查找表,或直接用真實(shí)的顏色值。GIF 圖像有一個(gè)顏色表 (或色彩面板),可以存儲(chǔ)最多 256 種顏色。存儲(chǔ)在位圖中的值是該查詢列表中的索引值,對(duì)應(yīng)著其相應(yīng)的顏色。所以,GIF 文件僅限于 256 色。對(duì)于簡(jiǎn)單的線條圖或純色圖,這是一種不錯(cuò)的解決方法。但對(duì)于照片來說,就會(huì)顯示的不夠真實(shí),照片需要更精細(xì)的顏色深度。進(jìn)一步的改進(jìn)是 PNG 文件,這種格式可以使用一個(gè)預(yù)置的色板或者獨(dú)立的通道,它們都支持可變的顏色深度。在一個(gè)通道中,每個(gè)像素的顏色分量 (紅,綠,藍(lán),即 RGB,有時(shí)添加透明度值,即RGBA) 是直接指定的。

GIF 和 PNG 對(duì)于具有大面積相同顏色的圖像是最好的選擇,因?yàn)樗鼈兪褂玫?(主要是基于游程長(zhǎng)度編碼的) 壓縮算法可以減少存儲(chǔ)需求。這種壓縮是無損的,這意味著圖像質(zhì)量不會(huì)被壓縮過程影響。

一個(gè)有損壓縮圖像格式的例子是 JPEG。創(chuàng)建 JPEG 圖像時(shí),通常會(huì)指定一個(gè)與圖像質(zhì)量相關(guān)的壓縮比值參數(shù),壓縮程度過高會(huì)導(dǎo)致圖像質(zhì)量惡化。JPEG 不適用于對(duì)比鮮明的圖像 (如線條圖),其壓縮方式對(duì)類似區(qū)域的圖像質(zhì)量損害會(huì)相對(duì)嚴(yán)重。如果某張截圖中包含了文本,且保存為 JPEG 格式,就可以清楚地看到:生成的圖像中字符周圍會(huì)出現(xiàn)雜散的像素點(diǎn)。在大部分照片中不存在這個(gè)問題,所以照片主要使用 JPEG 格式。

總結(jié):就放大縮小而言,矢量格式 (如 SVG) 是最好的。對(duì)比鮮明且顏色數(shù)量有限的線條圖最適合 GIF 或 PNG (其中 PNG 更為強(qiáng)大),而照片,則應(yīng)該使用 JPEG。當(dāng)然,這些都不是不可逾越的規(guī)則,不過通常而言,對(duì)一定的圖像質(zhì)量與圖像尺寸而言,遵守規(guī)則會(huì)得到最好的結(jié)果。

處理圖像數(shù)據(jù)

在 iOS 中有幾個(gè)類是用來處理位圖數(shù)據(jù)的:UIImage (UIKit),CGImage (Core Graphics) 和 CIImage (Core Image)。在創(chuàng)建以上類的實(shí)例之前,由 NSData 持有實(shí)際的數(shù)據(jù)。獲得一個(gè) UIImage 最簡(jiǎn)單的方法是使用 imageWithContentsOfFile: 方法,或者根據(jù)其來源,也可以使用 imageWithCGImage:imageWithCIImage: 或者 imageWithData:。有多個(gè)不同卻相似的類看起來有點(diǎn)多余,部分原因是因?yàn)樗鼈儊碜圆煌目蚣?,?duì)于圖像儲(chǔ)存的優(yōu)化方式也各有側(cè)重,通常情況下,不同類型之間是可以輕松轉(zhuǎn)換的。

從相機(jī)捕獲圖像

為了從相機(jī)得到一個(gè)圖像,我們需要?jiǎng)?chuàng)建一個(gè) AVCaptureStillImageOutput 對(duì)象。然后,我們可以使用 captureStillImageAsynchronouslyFromConnection: completionHandler: 方法。它的 handler 是以一個(gè) CMSampleBufferRef 類型為參數(shù)的 block。我們可以利用 AVCaptureStillImageOutput 中的類方法 jpegStillImageNSDataRepresentation: 將其轉(zhuǎn)換為一個(gè) NSData 對(duì)象, 接著,我們使用 imageWithData: (上文提到過) 來得到一個(gè) UIImage。在這個(gè)過程中,有許多參數(shù)可以調(diào)整,例如曝光控制或聚焦設(shè)定,光補(bǔ)償,閃光燈,甚至 ISO 設(shè)置 (僅 iOS 8)。設(shè)置會(huì)被應(yīng)用到一個(gè) AVCaptureDevice,這個(gè)對(duì)象代表著一個(gè)存在于設(shè)備上的相機(jī)。

用程序操作圖像

簡(jiǎn)單的圖像處理方法是使用 UIKit 的 UIGraphicsBeginImageContext 方法。調(diào)用之后,你就可以在當(dāng)前圖形上下文中繪制一些內(nèi)容,當(dāng)然也包括圖像本身。在我自己的 App —— Stereogram 中,我使用這個(gè)方法把兩個(gè)方形的圖像拼接,并在圖片上方添加了一個(gè)區(qū)域,放置了兩個(gè)點(diǎn)用于強(qiáng)調(diào)。代碼如下:

-(UIImage*)composeStereogramLeft:(UIImage *)leftImage right:(UIImage *)rightImage
{
    float w = leftImage.size.width;
    float h = leftImage.size.height;
    UIGraphicsBeginImageContext(CGSizeMake(w * 2.0, h + 32.0));
    [leftImage drawAtPoint:CGPointMake(0.0, 32.0)];
    [rightImage drawAtPoint:CGPointMake(w, 32.0)];
    float leftCircleX = (w / 2.0) - 8.0;
    float rightCircleX = leftCircleX + w;
    float circleY = 8.0;
    [[UIColor blackColor] setFill];
    UIRectFill(CGRectMake(0.0, 0.0, w * 2.0, 32.0));

    [[UIColor whiteColor] setFill];
    CGRect leftRect = CGRectMake(leftCircleX, circleY, 16.0, 16.0);
    CGRect rightRect = CGRectMake(rightCircleX, circleY, 16.0, 16.0);
    UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:leftRect];
    [path appendPath:[UIBezierPath bezierPathWithOvalInRect:rightRect]];
    [path fill];
    UIImage *savedImg = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return savedImg;
}

在畫布上排布好圖像并添加了兩個(gè)實(shí)心圓后,我們就可以調(diào)用一個(gè)方法把之前繪制好的圖形環(huán)境轉(zhuǎn)換成一個(gè) UIImage。該方法的輸出如下所示:

http://wiki.jikexueyuan.com/project/objc/images/21-11.jpg" alt="" />

這張圖片由兩臺(tái)位置稍有不同的相機(jī)拍攝的兩張照片,以及一條具有兩個(gè)中心白點(diǎn)的用來輔助觀察的黑色條帶合成而來。

相較而言,將實(shí)際的像素混合排布是一件麻煩的事情。在一張立體圖像中,我們有兩張相鄰的照片,我們需要使我們的眼睛 (有時(shí)可以靠斗眼) 看到 3D 效果。還有一種選擇是所謂的 紅藍(lán) 3D(Anaglyphs),你可以使用彩色 3D 眼鏡觀看的紅/綠圖像。(下面列出的函數(shù),實(shí)現(xiàn)了鏈接中的 Optimized Anaglyphs 方法。)

在以這種方式處理單獨(dú)的像素時(shí),我們要使用 CGBitmapContextCreate 方法創(chuàng)建一個(gè)位圖繪制環(huán)境,它包括一個(gè)顏色空間 (例如 RGB)。然后,我們可以遍歷位圖 (包括左邊和右邊的照片),并獲得在各個(gè)顏色通道值。例如,我們維持一張圖片中綠色和藍(lán)色的原值,然后將藍(lán)色和綠色的值按一定方式計(jì)算后賦值給另一張圖片的紅色值:

UInt8 *rightPtr = rightBitmap;
UInt8 *leftPtr = leftBitmap;
UInt8 r1, g1, b1;
UInt8 r2, g2, b2;
UInt8 ra, ga, ba;

for (NSUInteger idx = 0; idx < bitmapByteCount; idx += 4) {
    r1 = rightPtr[0]; g1 = rightPtr[1]; b1 = rightPtr[2];
    r2 = leftPtr[0]; g2 = leftPtr[1]; b2 = leftPtr[2];

    // r1/g1/b1 右側(cè)圖像,用于計(jì)算合并值
    // r2/g2/b2 左側(cè)圖像,用于被合并值賦值
    // ra/ga/ba 合并后的像素

    ra = 0.7 * g1 + 0.3 * b1;
    ga = b2;
    ba = b2;

    leftPtr[0] = ra;
    leftPtr[1] = ga;
    leftPtr[2] = ba;
    rightPtr += 4; // 指向下一個(gè)像素 (4字節(jié), 包括透明度 alpha 值)
    leftPtr += 4;
}

CGImageRef composedImage = CGBitmapContextCreateImage(_leftContext);
UIImage *retval = [UIImage imageWithCGImage:composedImage];
CGImageRelease(composedImage);
return retval;

在這個(gè)方法中,我們可以訪問實(shí)際的像素的所有信息,做任何我們喜歡的事情。不過,最好先查詢 Core Image 中是否已經(jīng)有濾鏡可用,因?yàn)樗鼈兪褂酶菀?,并且在通常情況下,它們比一些單像素值的處理方式更優(yōu)。

元數(shù)據(jù)

用于存儲(chǔ)圖像信息的標(biāo)準(zhǔn)格式是 Exif(可交換圖像文件格式)。在照片中,通常會(huì)捕獲照相的日期和時(shí)間,快門速度和光圈,如果設(shè)備支持,還包括 GPS 坐標(biāo)。這是基于 TIFF(標(biāo)簽圖像文件格式) 的一種標(biāo)簽系統(tǒng)。雖然它有很多缺陷,可作為一個(gè)現(xiàn)行標(biāo)準(zhǔn),確實(shí)沒有更好的選擇。通常情況下,有些方式會(huì)設(shè)計(jì)的更好,但我們大家所使用的相機(jī)都不支持。

在 iOS 中,可以使用 CGImageSourceCopyPropertiesAtIndex 方法訪問 Exif 信息。這個(gè)方法會(huì)返回一個(gè)包含所有相關(guān)信息的字典。不過,不要過于依賴這些附加的信息。由于供應(yīng)商定義的擴(kuò)展約定 (這不是一個(gè)通用的標(biāo)準(zhǔn)) 錯(cuò)綜復(fù)雜,數(shù)據(jù)經(jīng)常被丟失或損壞,尤其是當(dāng)圖像經(jīng)過許多不同的應(yīng)用程序 (如圖像編輯器等) 處理過后。一般來說,當(dāng)圖像被上傳到網(wǎng)絡(luò)服務(wù)器上時(shí),這些信息也會(huì)被抹掉;這些信息中有一些屬于敏感信息,例如 GPS 數(shù)據(jù)。出于對(duì)隱私的保護(hù),這些信息常常會(huì)被刪除。顯然,NS??A (美國(guó)國(guó)家安全局) 就在收割它們 XKeyscore 程序中的 Exif 數(shù)據(jù)。

總結(jié)

處理圖像在有些時(shí)候是一個(gè)相當(dāng)復(fù)雜的問題。圖像處理這個(gè)問題已經(jīng)存在了一段時(shí)間,所以針對(duì)不同方面,會(huì)存在許多不同的框架。有時(shí)候,你不得不深入底層的 C 函數(shù)調(diào)用,解決隨之而來的手動(dòng)內(nèi)存管理問題。再者,圖像的來源很多,每個(gè)來源都會(huì)需要處理特定且不同的邊界問題。不過,iOS 中最大的問題其實(shí)是內(nèi)存:隨著相機(jī)和屏幕分辨率越來越好,圖像的尺寸也開始變大。iPhone 5S 有一個(gè) 800 萬像素的攝像頭;如果每個(gè)像素被存儲(chǔ)在4個(gè)字節(jié) (分別用于三個(gè)顏色信道,加上一個(gè)不透明性),就會(huì)產(chǎn)生 32 兆的數(shù)據(jù)。如果需要添加幾個(gè)工作副本或圖像的預(yù)覽,我們會(huì)很快在處理??多張圖片或幻燈片時(shí)遇到麻煩。再加上文件系統(tǒng)的寫入也并不是非常快,所以很有必要進(jìn)行一些優(yōu)化,以確保你的 iOS 應(yīng)用程序運(yùn)行流暢。