iOS繪制1像素線的正確姿勢

一、前言

事情的起因是這樣的踩娘,因?yàn)樾枨蟮脑蚬涡蹋幸粋€(gè)頁面的cell分割線需要自定義,于是我的同事很順其自然地用了個(gè)view养渴,并將其高度設(shè)為1雷绢,來作為cell分割線使用。一切看起來都那么平靜理卑,直到有一天翘紊,產(chǎn)品大大的出現(xiàn),打破了所有的寧靜藐唠。他帆疟,一個(gè)帶有血輪眼的男人,看出了高度為1的線與系統(tǒng)cell分割線的不同宇立。如果他要是看了這篇文章的話踪宠,他就應(yīng)該能明白我說的那句話的含義。3倍屏上高度為1的線與高度為1像素的線差別只是在2個(gè)像素的粗細(xì)妈嘹,基本上已經(jīng)超出了肉眼的識別能力柳琢。他,是一個(gè)傳奇,一個(gè)可以與水哥抗衡的真漢子


~.png

好吧柬脸,言歸正傳他去,下面就來分析下高度為1的view線與系統(tǒng)cell分割線的區(qū)別在哪兒吧。在此之前先要搞懂下面這兩坨東西倒堕。

二灾测、Point與Pixel

2.1 Point與Pixel的概念
  • 先來說下Pixel,翻譯過來就是像素垦巴,屏幕上顯示的最小單位媳搪。
  • Point,翻譯過來就是點(diǎn)骤宣,是一個(gè)標(biāo)準(zhǔn)的長度單位蛾号。在編程中,frame涯雅、bounds、center等設(shè)置的坐標(biāo)位置就是以point為單位展运。
2.2 Point與Pixel的關(guān)系

iPhone 4之前 non-retina 屏幕的設(shè)備,一個(gè)point 就代表一個(gè)像素活逆,在此就不做過多說明。之后的retina屏幕(視網(wǎng)膜屏)拗胜,兩者之間的關(guān)系見下表:

設(shè)備 尺寸 scale
iPhone4s 320,480 2
iPhone5/5s 320,568 2
iPhone6 375,667 2
iPhone6s 414,736 2
iPhone6plus 414,736 3
... ... 3

scale根據(jù)代碼可以獲取:

CGFloat scale = [UIScreen mainScreen].scale

基于以上信息可以看出蔗候,我們大部分情況下都不需要去關(guān)注pixel,然而存在部分情況需要考慮像素的轉(zhuǎn)化埂软。比如說繪制一個(gè)1像素粗細(xì)的線锈遥。
看到這個(gè)問題,第一想法就是根據(jù)當(dāng)前屏幕的縮放因子scale計(jì)算出1像素線對應(yīng)的點(diǎn)勘畔,然后將其設(shè)置成線的粗細(xì)即可所灸。
沒錯(cuò),我當(dāng)時(shí)就這么干了炫七。代碼寫完爬立,編譯運(yùn)行發(fā)現(xiàn)在設(shè)備上有的線并沒有顯示出來。
我在萬能的互聯(lián)網(wǎng)上找到了原因:
為了獲得良好的視覺效果万哪,繪圖系統(tǒng)通常都會采用一個(gè)叫antialiasing(反鋸齒)的技術(shù)侠驯,iOS也不例外。顯示屏幕有很多個(gè)顯示單元(即像素)組成奕巍,如果要畫一條黑線吟策,這條線剛好落在了一列或者一行顯示單位之內(nèi),將會渲染出標(biāo)準(zhǔn)的一個(gè)像素的黑線的止。如下圖所示:

渲染出標(biāo)準(zhǔn)黑線.jpg

但是如果線落在樂兩個(gè)行或列的中間時(shí)檩坚,那么會得到一條失真的線,如下圖所示:
1Point的線卡在兩個(gè)像素之間.jpg

官方給出的解釋與解決辦法是這樣的

解釋:
Positions defined by whole-numbered points fall at the midpoint between pixels. For example, if you draw a one-pixel-wide vertical line from (1.0, 1.0) to (1.0, 10.0), you get a fuzzy grey line. If you draw a two-pixel-wide line, you get a solid black line because it fully covers two pixels (one on either side of the specified point). As a rule, lines that are an odd number of physical pixels wide appear softer than lines with widths measured in even numbers of physical pixels unless you adjust their position to make them cover pixels fully.
奇數(shù)像素寬度的線在渲染的時(shí)候?qū)憩F(xiàn)為柔和的寬度擴(kuò)展到向上的整數(shù)寬度的線,除非你手動的調(diào)整線的位置效床,使線剛好落在一行或列的顯示單元內(nèi)睹酌。

解決辦法

On a low-resolution display (with a scale factor of 1.0), a one-point-wide line is one pixel wide. To avoid antialiasing when you draw a one-point-wide horizontal or vertical line, if the line is an odd number of pixels in width, you must offset the position by 0.5 points to either side of a whole-numbered position. If the line is an even number of points in width, to avoid a fuzzy line, you must not do so.
On a high-resolution display (with a scale factor of 2.0), a line that is one point wide is not antialiased at all because it occupies two full pixels (from -0.5 to +0.5). To draw a line that covers only a single physical pixel, you would need to make it 0.5 points in thickness and offset its position by 0.25 points. A comparison between the two types of screens is shown in Figure 1-4.
1.在non-retina屏上,一個(gè)Point對應(yīng)一個(gè)像素剩檀。為了防止antialiasing導(dǎo)致的奇數(shù)像素的線渲染時(shí)出現(xiàn)失真憋沿,你需要設(shè)置偏移0.5個(gè)點(diǎn)。
2.在retina屏上沪猴,要繪制一個(gè)像素的線辐啄,需要設(shè)置線寬為0.5個(gè)點(diǎn),同時(shí)設(shè)置偏移為0.25個(gè)點(diǎn)运嗜。
3.如果線寬為偶數(shù)Point的話壶辜,則不要去設(shè)置偏移,否則線條也會失真担租。

至此似懂非懂地貌似察覺到了解決辦法砸民,但是上面給出的解決方法需要偏移的點(diǎn)數(shù)也只是在二倍屏的基礎(chǔ)上。如果為三倍屏奋救,又該偏移多少呢岭参?
下面就來分析下偏移量的計(jì)算。
首先設(shè)置為1Pixel的線尝艘,其所用的點(diǎn)為1 / [UIScreen mainScreen].scale演侯。渲染的時(shí)候,如果我們檢測到線的落點(diǎn)并沒有完美顯示在顯示單元上(奇數(shù)單元點(diǎn))背亥,只需要將其移動半個(gè)單元到偶數(shù)單元點(diǎn)秒际,即可使線渲染的時(shí)候躲過** antialiasing**這個(gè)門檻。
即1像素的線需要的偏移量為1 / [UIScreen mainScreen].scale/2

三狡汉、核心代碼

下面貼上繪制1像素線的核心代碼:

- (void)drawRect:(CGRect)rect
{
    [super drawRect:rect];
    CGContextRef context = UIGraphicsGetCurrentContext();
    
    CGContextBeginPath(context);
    CGFloat pixelAdjustOffset = 0;
    // 落在奇數(shù)位置的顯示單元上
    if (((int)(1* [UIScreen mainScreen].scale) + 1) % 2 == 0)
    {
        pixelAdjustOffset = SINGLE_LINE_ADJUST_OFFSET;
    }
    // 設(shè)置畫線y值
    CGFloat yPos = 1 - pixelAdjustOffset;
    // 如果想在view的最底部畫線娄徊,需設(shè)置lineMode為EUCPixelViewLineModeBottom
    if (self.lineMode == EUCPixelViewLineModeBottom)
    {
        while (yPos + 1 < self.bounds.size.height) {
            yPos ++;
        }
    }
    CGContextMoveToPoint(context, 0, yPos);
    CGContextAddLineToPoint(context, self.bounds.size.width, yPos);
    
    CGContextSetLineWidth(context, 1);
    CGContextSetStrokeColorWithColor(context, self.lineColor.CGColor);
    CGContextStrokePath(context);
}

有時(shí)間補(bǔ)上github上的demo地址
謝謝觀看!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末盾戴,一起剝皮案震驚了整個(gè)濱河市嵌莉,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌捻脖,老刑警劉巖锐峭,帶你破解...
    沈念sama閱讀 222,590評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異可婶,居然都是意外死亡沿癞,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,157評論 3 399
  • 文/潘曉璐 我一進(jìn)店門矛渴,熙熙樓的掌柜王于貴愁眉苦臉地迎上來椎扬,“玉大人惫搏,你說我怎么就攤上這事〔系樱” “怎么了筐赔?”我有些...
    開封第一講書人閱讀 169,301評論 0 362
  • 文/不壞的土叔 我叫張陵,是天一觀的道長揖铜。 經(jīng)常有香客問我茴丰,道長,這世上最難降的妖魔是什么天吓? 我笑而不...
    開封第一講書人閱讀 60,078評論 1 300
  • 正文 為了忘掉前任贿肩,我火速辦了婚禮,結(jié)果婚禮上龄寞,老公的妹妹穿的比我還像新娘汰规。我一直安慰自己,他們只是感情好物邑,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,082評論 6 398
  • 文/花漫 我一把揭開白布溜哮。 她就那樣靜靜地躺著,像睡著了一般色解。 火紅的嫁衣襯著肌膚如雪茬射。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,682評論 1 312
  • 那天冒签,我揣著相機(jī)與錄音,去河邊找鬼钟病。 笑死萧恕,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的肠阱。 我是一名探鬼主播票唆,決...
    沈念sama閱讀 41,155評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼屹徘!你這毒婦竟也來了走趋?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 40,098評論 0 277
  • 序言:老撾萬榮一對情侶失蹤噪伊,失蹤者是張志新(化名)和其女友劉穎簿煌,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體鉴吹,經(jīng)...
    沈念sama閱讀 46,638評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡姨伟,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,701評論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了豆励。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片夺荒。...
    茶點(diǎn)故事閱讀 40,852評論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出技扼,到底是詐尸還是另有隱情伍玖,我是刑警寧澤,帶...
    沈念sama閱讀 36,520評論 5 351
  • 正文 年R本政府宣布剿吻,位于F島的核電站窍箍,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏和橙。R本人自食惡果不足惜仔燕,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,181評論 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望魔招。 院中可真熱鬧晰搀,春花似錦、人聲如沸办斑。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,674評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽乡翅。三九已至鳞疲,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間蠕蚜,已是汗流浹背尚洽。 一陣腳步聲響...
    開封第一講書人閱讀 33,788評論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留靶累,地道東北人腺毫。 一個(gè)月前我還...
    沈念sama閱讀 49,279評論 3 379
  • 正文 我出身青樓,卻偏偏與公主長得像挣柬,于是被迫代替她去往敵國和親潮酒。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,851評論 2 361

推薦閱讀更多精彩內(nèi)容