美文网首页
小于1像素的渲染探究

小于1像素的渲染探究

作者: 张_何 | 来源:发表于2020-08-31 18:40 被阅读0次

名词解释

  • 1 英寸=2.54 厘米

  • PPI:Pixels Per Inch也叫像素密度,所表示的是每英寸所拥有的像素数量

  • DPI:Dots Per Inch,每英寸点数,图像每英寸长度内的像素点

  • pt (point,磅):是一个物理长度单位,指的是72分之一英寸。

  • px (pixel,像素):是一个虚拟长度单位,是计算机系统的数字化图像长度单位,如果px要换算成物理长度,需要指定精度DPI(Dots Per Inch,每英寸像素数),

  • DPR:设备像素比,DPR = 单位长度内(pixel/point)

  • 当像素密度超过300ppi时,人眼就无法区分出单独的像素,retina屏的像素密度达到326ppi,肉眼观看的时候不会再出现颗粒感

  • 重采样技术:一种影像数据处理方法。即影像数据重新组织过程中的灰度处理方法。影像采样是按一定间隔采集影像灰度数值的,当阈值不位于采样点上的原始函数的数值时,就需要利用已采样点进行内插,称为重采样

背景

  • 在15年的时候,有个设计是加一条分隔线,当时在两倍屏上线的高度如果设置小于0.5时,在有些情况下显示不出来,当时理解为两倍屏的0.5宽度对应的就是一个物理像素的宽度,当小于一个物理像素的时候就显示不出来了。当时网上流行两种种处理方法,比如加盐、设置偏移。通常设置一像素的线大家采用的是定义一个宏,#define LINEWIDTH (1/[UIScreen main].scale) 这样能保证不论是在两倍屏还是三倍屏行线都是一像素的。

  • 最近搞Flutter开发,惊奇的发现设置一条线的高度为0.1,Flutter竟然也能显示,很好奇Flutter是怎么做到的。于是乎开始了我的探索之路

Flutter 测试

class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("测试"),
      ),
      body: Container(
        color: Colors.white,
        child: Center(
          child: Column(
            children: <Widget>[
              _line(0.1),
              _line(0.2),
              _line(0.3),
              _line(0.4),
              _line(0.5),
              _line(0.6),
              _line(0.9),
              _line(1.0),
              _line(1.1),
              _line(1.4),
              _line(1.5),
              _line(1.9),
              _line(2.0),
              _line(5.0),
            ],
          ),
        ),
      ),
    );
  }

  Widget _line(double height){
    return Container(
      child: Row(
        children: <Widget>[
          Container(
            margin: EdgeInsets.only(left: 20,right: 10),
            child: Text("$height"),
          ),
//          _containerLine(height),
          _drawLine(height),
        ],
      ),
    );
  }
  // 测试用container 做的line
  Widget _containerLine(double height){
    return Container(
      height: 15,
      width: 200,
      color: Colors.white,
      child: Center(
        child: Container(
          height: height,
          width: 375,
          color: Colors.red,
        ),
      ),
    );
  }
//  测试画出来的线
  Widget _drawLine(double height){
    return CustomPaint(
      size: Size(250, 20),
      painter:TestPainter(height) ,
    );
  }
}
  • 首先用flutter Container做个line测试效果
    下图是总体效果


    WechatIMG22.jpeg
看一下0.2~0.5线的宽度是一样的,不过颜色在逐渐加重.png 从1.0~2.0的过程是线上下两边颜色逐渐由浅变深的过程,2.0时可以明显看出线的高度是1.0的两倍.png
  • 然后使用flutter 绘制出一根线做测试

下图看起来和使用Container做的线显示效果一样


WechatIMG24.jpeg 0.1~0.5线的宽度没变,颜色在逐渐加重.png 0.5~1.0线的宽度没变,颜色在逐渐加重.png 2.0是1.0线高的两倍.png
  • 通过上面的测试结果我们可以看出
    将图片放大后发现线的height 0.1~1.0 渲染出来的高度是一样的,只不过显示颜色深浅的不一样,从1.0~2.0的过程是线上下两边颜色逐渐由浅变深的过程,2.0时可以明显看出线的高度是1.0的两倍。

Flutter 官方对解释的不是很清楚

ios原生测试

#import "ViewController.h"

@interface RenderView: UIView

@end

@implementation RenderView

-(void)drawRect:(CGRect)rect{
    [super drawRect:rect];
    
    CGContextRef context = UIGraphicsGetCurrentContext();
    
    [self drawLine:context lineHight:0.1 startPoint:CGPointMake(50, 50) endPoint:CGPointMake(200, 50)];
    [self drawLine:context lineHight:0.2 startPoint:CGPointMake(50, 60) endPoint:CGPointMake(200, 60)];
    [self drawLine:context lineHight:0.3 startPoint:CGPointMake(50, 70) endPoint:CGPointMake(200, 70)];
    [self drawLine:context lineHight:0.5 startPoint:CGPointMake(50, 80) endPoint:CGPointMake(200, 80)];
    [self drawLine:context lineHight:0.7 startPoint:CGPointMake(50, 90) endPoint:CGPointMake(200, 90)];
    [self drawLine:context lineHight:0.9 startPoint:CGPointMake(50, 100) endPoint:CGPointMake(200, 100)];
    [self drawLine:context lineHight:1.0 startPoint:CGPointMake(50, 110) endPoint:CGPointMake(200, 110)];
    [self drawLine:context lineHight:1.05 startPoint:CGPointMake(50, 120) endPoint:CGPointMake(200, 120)];
    [self drawLine:context lineHight:1.2 startPoint:CGPointMake(50, 130) endPoint:CGPointMake(200, 130)];
    [self drawLine:context lineHight:1.4 startPoint:CGPointMake(50, 140) endPoint:CGPointMake(200, 140)];
    [self drawLine:context lineHight:1.5 startPoint:CGPointMake(50, 150) endPoint:CGPointMake(200, 150)];
    [self drawLine:context lineHight:1.7 startPoint:CGPointMake(50, 160) endPoint:CGPointMake(200, 160)];
    [self drawLine:context lineHight:1.9 startPoint:CGPointMake(50, 170) endPoint:CGPointMake(200, 170)];
    [self drawLine:context lineHight:2.0 startPoint:CGPointMake(50, 180) endPoint:CGPointMake(200, 180)];
    [self drawLine:context lineHight:5.0 startPoint:CGPointMake(50, 190) endPoint:CGPointMake(200, 190)];
}

-(void)drawLine:(CGContextRef )context lineHight:(CGFloat)height startPoint:(CGPoint)start endPoint:(CGPoint)end{
    CGContextMoveToPoint(context, start.x, start.y);
    CGContextAddLineToPoint(context, end.x, end.y);
    CGContextSetLineWidth(context,height);
    CGContextClosePath(context);
    [[UIColor redColor]setStroke];
    CGContextDrawPath(context, kCGPathStroke);
    
    NSString *string = [NSString stringWithFormat:@"%.2f",height];
    UIFont *fount = [UIFont systemFontOfSize:9];
    [string drawInRect:CGRectMake(20, start.y-10, 30, 20) withAttributes: @{NSFontAttributeName:fount}];
}
@end

@interface ViewController ()
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.view.backgroundColor = [UIColor whiteColor];
    
//    RenderView *v = [[RenderView alloc] init];
//    v.frame = CGRectMake(0, 0, 300, 500);
//    v.backgroundColor = [UIColor whiteColor];
//    [self.view addSubview:v];
//
    float left = 50;
    [self lineRect:CGRectMake(left, 50, 100, 1)];
    [self lineRect:CGRectMake(left, 60, 100, 0.1)];
    [self lineRect:CGRectMake(left, 70, 100, 0.2)];
    [self lineRect:CGRectMake(left, 80, 100, 0.3)];
    [self lineRect:CGRectMake(left, 90, 100, 0.5)];
    [self lineRect:CGRectMake(left, 100, 100, 0.7)];
    [self lineRect:CGRectMake(left, 110, 100, 0.8)];
    [self lineRect:CGRectMake(left, 120, 100, 0.9)];
    [self lineRect:CGRectMake(left, 130, 100, 1.0)];
    [self lineRect:CGRectMake(left, 140, 100, 1.05)];
    [self lineRect:CGRectMake(left, 150, 100, 1.2)];
    [self lineRect:CGRectMake(left, 160, 100, 1.4)];
    [self lineRect:CGRectMake(left, 170, 100, 1.5)];
    [self lineRect:CGRectMake(left, 180, 100, 1.7)];
    [self lineRect:CGRectMake(left, 190, 100, 1.9)];
    [self lineRect:CGRectMake(left, 200, 100, 2.0)];
    [self lineRect:CGRectMake(left, 210, 100, 5.0)];
}

-(void)lineRect:(CGRect) rect{
    UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(20, rect.origin.y - 10, 30, 20)];
    label.text = [NSString stringWithFormat:@"%.1f",rect.size.height];
    label.font = [UIFont systemFontOfSize:9];
    [self.view addSubview:label];
    
    UIView  *v = [[UIView alloc] initWithFrame:rect];
    v.backgroundColor = [UIColor redColor];
    [self.view addSubview:v];
}
@end
  • 我们先测试一下使用UIView做根线的效果
图中我们看到当设置线的高度为0.1和0.2时并没有渲染出来.jpeg 图片放大后看到0.3到0.7线的宽度和颜色是一样的,可能是截图的原因,实际上0.3到0.7的颜色手机上看并没有显示的那么暗,高度确实比0.8的矮了一倍.png 1.4到1.7线的高度可以看出来比1.0时高了50%,注意是50%而不是一倍,1.9和2.0是确实比1.0时高了一倍,线上下边缘的颜色没有变,但内部的颜色值变从1.4到5.0都是一样的.png
  • 我们在用ios原生画一条线测试一下


    WechatIMG27.jpeg
放大后我们发下线的颜色也是由浅到深的变化,但是高度是每边的.png 从1.0到2.0的过程中,线的宽度是变宽了,不过随着线上下边缘颜色的逐渐加深,到2.0时可以明显看出2.0的线高比1.0确实高了一倍.png

总结

通过测试对比发现Flutter在渲染小于1逻辑像素的线的时候,其高度是按1逻辑像素去展示的,只不过看到的颜色会不一样,当设置线的高度不是整数逻辑像素时,线的高度是线上取整后的逻辑像素,只不过看到的线上下边缘的颜色会随着小数部分的值越大颜色越接近设置的颜色。也就是说flutter是以1逻辑像素的整数倍去显示的,如果是非整数倍逻辑像素线上下边缘的颜色会跟设置的不一样(以灰度的方式显示出来)。

ios 原生目前也可以展示小于1物理像素的点,但是以UIView的控件做的线,其高度有个临界值,低于临界值的线会显示不出来,而且其显示的线边缘比较锐利,其高度在到某一临界值后会直接变高,而不是在线上下边缘做灰度展示。而用原生绘制出来的线展示小于以逻辑像素的时候其高度均展示为1逻辑像素,不过颜色值会以灰度的方式展示出来,在达到逻辑像素的整数倍时展示设置的颜色,这是和flutter一样的。

结语

  • 这次探究推翻了我之前的认知,以前我先是把逻辑像素转换成对应的物理像素去展示,认为小于一物理像素的点是不能展示出来的。现在认识到不要强行将物理像素和逻辑像素联系在一起,当线小于一逻辑像素的时候,渲染的时候并不是物理像素的点就是暗的,其实每一个物理像素是有红黄蓝三种颜色组成,每种颜色有8位来控制,也就是一个物理像素可以展示2的24次方种颜色,当逻辑像素小于1时,渲染的时候内部会有自己的算法将其对应的物理像素点以灰度的方式展示,这样就能展示小于1逻辑像素的点了。

比如拿iPhone 6s 举例,其屏幕宽度是2.3英寸,横向上有750颗像素点,ppi = 750/2.3 = 326,既每寸上有326个物理像素,换算成厘米也就是 326/2.54 = 128.35,也就是说每厘米上有128.35个物理像素点,每毫米有13个物理像素点,也就是每个物理像素点的宽度是0.077毫米。6s 的逻辑宽度是375,横向上每厘米有375/(2.3*2.54) = 64逻辑像素/cm,也就是6s上每个逻辑像素的宽度是0.156毫米。当我们设置一逻辑像素宽度时其在6s上对应的宽度是0.156毫米。

相关文章

  • 小于1像素的渲染探究

    名词解释 1 英寸=2.54 厘米 PPI:Pixels Per Inch也叫像素密度,所表示的是每英寸所拥有的像...

  • 离屏渲染的探究

    什么是离屏渲染 作为一个 iOS 开发者,你肯定听说过离屏渲染,什么是离屏渲染呢? 案例 在模拟器上显示一张裁剪的...

  • Octane渲染器学习笔记之二:照明-1

    【前言】 对于渲染器来说,没有光源,渲染时将一片漆黑。这篇我将探究一下OC渲染器的光源照明知识。对于物理渲染...

  • 2022-07-04

    iOS中的异步渲染探究,以及基于异步渲染的高度自定富文本框架构建 此前一直犹豫要不要写个自己的异步渲染库,最近赶上...

  • iOS屏幕撕裂、屏幕卡顿、离屏渲染的相关探究

    这篇文章我们来探究下屏幕撕裂、屏幕卡顿、离屏渲染。 一、屏幕撕裂 在探究屏幕撕裂问题之前,我们需要先了解下屏幕显示...

  • iOS离屏渲染的原因

    cornerRadius和masksToBounds组合探究离屏渲染 我们可以在模拟器下,开启color Off-...

  • Vue源码探究-虚拟DOM的渲染

    Vue源码探究-虚拟DOM的渲染 在虚拟节点的实现一篇中,除了知道了 VNode 类的实现之外,还简要地整理了一下...

  • ios 离屏渲染的深入探究

    1. 定义 在ios中,普通的渲染是这样的,假设我们的APP每秒显示60帧的数据,这个数据是存在我们的Frame ...

  • iOS 视图,动画渲染机制探究

    文章写的很实用,细节都很到位,我又邪恶的转载了 原文地址:http://www.cnblogs.com/bugly...

  • iOS 深入探究离屏渲染

    一、离屏渲染检测 用Xcode创建一个新的工程,然后在AppDelegate.m添加如下代码: 创建一个UIVie...

网友评论

      本文标题:小于1像素的渲染探究

      本文链接:https://www.haomeiwen.com/subject/wqgusktx.html