• 2019-04-03

    苹果 MacBook Pro 屏幕缺陷导致蓝色显示异常条纹

    Views: 33526 | 4 Comments

    最近我的 MacBook Pro 因为屏幕涂层脱落, 让苹果 Genius Bar 免费更新之后, 我发现更换后的屏幕显示效果很差, 像素颗粒非常明显, 没有苹果引以为傲的 Retina 屏幕的那种细腻效果. 我甚至一度怀疑给我更换的是一个 10 年前技术的 PC 显像管显示器. 这个问题还好, 只要不抠像素, 眼睛离屏幕稍微远一点, 就察觉不到像素了.

    前几天, 在做 Keynote 和表格的时候, 我突然发现 MacBook Pro 的显示器竟然在显示蓝色的时候出现细微的灰色条纹. 这个问题是怎么发现的呢?

    在 Keynote 里添加一个默认颜色(蓝色)的方框, 然后靠近仔细观察, 你就会发现蓝色的纯色在一条一条的灰色条纹, 像是像素之间的间隙透光. 这个蓝色不是纯蓝(#0000ff), 而是带有绿色和黄色的蓝色(大概是 #3388ff).

    blue

    之后, 对比了多台同型号同年份且没有更新过涂层脱落屏幕的 MacBook Pro, 最终确认新的苹果给换的屏幕确实不够细腻, 可以认定是低品质的.

    特别是在看 Firefox 浏览器的 LOGO 图标时, 你会发现火狐抱着的中间那个蓝色的地球, 条纹最明显!

    firefox

    我外行地分析了一下, 苹果屏幕用的 LED 屏, 每个像素由三个分别是红(R)绿(G)蓝(B)颜色的灯管组成, 通过三个颜色的灯管的不同亮度叠加, 产生不同的颜色. 如果三个灯管全部最亮, 就是白色了. 这个原理就是 Additive Color .

    RGB_illumination

    可能的原因

    有了上面的基本原理之后, MacBook Pro 低档屏幕显示蓝色出现白色条纹的原因就可以分析出来了.

    1. 生产工艺太差, LED 灯管排列间距太大.

    这个原因是很有可能. 苹果用的屏幕的品牌和型号等, 不像 PC 那么透明, 无法直观的对比不同型号的品质差异. 如果真是这个原因, 那么便是生产工艺缺陷, 导致两行像素之间的间隔太大.

    不过, 为什么蓝色(#3388ff)那么明显的. 应该涉及到单个像素的 RGB 三个灯管的排列方式.

    2. RGB 分组排列导致 R 和 G 出现在同一行

    我觉得屏幕生产厂商不太可能这么蠢, 除非有特殊考虑. 如果真的出现这种状况, 不同组的红灯和绿灯刚好组成了一条黄线, 那么出问题的蓝色(#3388ff)中的 R(33) G(88) 组成一条绿线, 同时受蓝色灯管影响, 就会变成浅白色的条纹了.

    外行分析, 如果有专业人数, 欢迎提供信息.

    Posted by ideawu at 2019-04-03 20:54:58
  • 2019-03-21

    CVPixelBufferRef 与 CVOpenGLTextureRef: 图像处理中内存与显存的交互

    Views: 31114 | No Comments

    现代计算机系统在进行图像处理时,可以利用 CPU 或者显卡两种芯片之一进行处理,也可以同时使用两者。本文讨论在 macOS 上的图像处理。

    1. 完全使用 CPU 进行图像处理

    图像(如 png)从硬盘中读取并解压到内存中,之后的图像处理完全用 CPU 操作内存,和显卡无关。

    2. 完全使用显卡进行图像处理

    图像(如 png)从硬盘中读取并解压到内存中,然后传到显卡显存中(创建 OpenGL texture),图像一旦传到显卡,内存中的图像数据就可以删除了。

    3. 同时使用 CPU 和显卡进行图像处理

    图像同时存于内存以及显存中,有时利用显卡进行处理,有时利用 CPU 进行处理。因为图像数据同时保存于内存和显存中,所以需要某种绑定机制,关联内存中的图像和显存中的图像,并在一方变更时,更新另一方。

    # CVPixelBufferRef 与 CVOpenGLTextureRef

    CVPixelBufferRef 表示内存中的图像。

    CVOpenGLTextureRef 表示显存中的图像,内部使用 OpenGL 的 texture。

    苹果的 Core Video 框架提供了显存图像到内存图像的单向实时绑定机制,也即绑定 CVPixelBufferRef 和 CVOpenGLTextureRef,当后者(显存)更新时,框架自动更新前者(内存)。注意,这种实时绑定是单向的,更新的传导只有一个方向,即从显存到内存。

    使用 CVOpenGLTextureCacheCreateTextureFromImage() 函数来建立这种绑定关系。绑定建立后,用户(也即你的代码)对显存或者内存的操作,都必须涉及到锁,因为框架本身同时也会操作这两份数据。

    涉及到多者同时操作一份数据的情况,都需要锁。

    使用 CVPixelBufferLockBaseAddress() 和 CVPixelBufferUnlockBaseAddress() 两个函数进行锁操作。显然,当你的代码要操作内存数据时,你应该 lock CVPixelBufferRef,当你想操作显存数据时,就应该 unlock CVPixelBufferRef。

    前面提到,这种单向绑定会将显存的更新传导到内存,所以,当你更新完显存(即执行 OpenGL glFinish 操作,文档提到是 glFlush 之后)之后,你就获得了 OpenGL 渲染的图像数据(OpenGL 截图),其内部实现应该是用 glReadPixels() 或者 glGetTexImage() 函数。这时,你就可以把 OpenGL 渲染的结果保存成 png 文件了。

    # 关于 Metal 框架的 CVMetalTextureRef

    CVMetalTextureRef 是用来替代 CVPixelBufferRef 的,因为苹果已经发布了 Metal 框架用来替代所有苹果操作系统上的 OpenGL。

    提一句,从面向对象的角度 CVPixelBufferRef CVOpenGLTextureRef CVMetalTextureRef 这三者都是 CVImageBufferRef 的子类。不过,在 C 语言里,这三者是同一个结构体,只不过用 typedef 命了 4 个名字而已。

    typedef CVImageBufferRef CVPixelBufferRef;
    typedef CVImageBufferRef CVOpenGLTextureRef;
    typedef CVImageBufferRef CVMetalTextureRef;
    

    CVImageBufferRef(也即 CVBufferRef)内部应该是用了 type 字段来表示不同的子类,毕竟 C 语言没有 C++ 那样的类和继承,只能用结构体来实现子类,都是惯用法,大同小异。

    如果你使用 Core Graphics, 那么从内存到显存的路径是这样的: CGImage => CGBitmapContext => OpenGL texture

    Posted by ideawu at 2019-03-21 17:07:19
  • 2019-03-13

    2018款MacBook Pro 15寸AMD Radeon Pro Vega 20显卡导致闪屏问题

    Views: 28568 | 3 Comments

    在至少两台这个显卡的2018款 MacBook Pro 15 寸电脑上已经测试发现,苹果用的最高性能的 AMD Radeon Pro Vega 20 显卡可能有驱动问题,或者硬件问题,会导致系统在 1440x900 分辨率下出现闪屏(瞬间花屏)的问题。

    这个闪屏问题很容易复现:

    * 把分辨率从默认的 1680x1050 改成 1440x900
    * 运行 iMovie,或者 Chess 等等使用高性能显卡的应用
    * 打开 App Store,搜索 iMovie 或者别的软件,然后播放软件的介绍视频

    这时你应该能发现屏幕隔几秒就花屏闪一次。如果不出现,你可以试着锁屏,等屏幕黑了之后,再登录系统,确保视频在播放着。

    Posted by ideawu at 2019-03-13 18:06:24
  • 2019-02-28

    macOS NSView 实现 drag and drop 拖放

    Views: 28001 | No Comments

    macOS 实现 drag and drop 拖放很复杂,因为苹果的开发者中心把很多 demo 代码都不知道放到哪里去了,你用苹果开发者网站找不到 Sample Code,用 Xcode 帮助也没法找到(补充:sample code 在 https://developer.apple.com/library/archive/navigation/#section=Resource%20Types&topic=Sample%20Code )。而通过搜索引擎找的话,各种例子不是老旧就是没讲对。

    其实 drag and drop 很简单。有两个概念:DraggingSource 和 DraggingDestination。你可以把拖放理解两个容器之间的交互,你要将某物从A拖到B然后放下。

    最重要的一点,不管实现什么接口什么方法,你必须告诉系统什么时候拖放开始了。我看了很多文章,都没有重点提到这一点。也就是说,不管你实现什么接口都没有用,你必须主动告诉系统拖放已经开始了。所以,在你的代码中明确地告诉系统这一点,一般是鼠标按住的时候。

    当你决定拖放已经开始了,就调用这个方法:

    // NSView 的方法,当你决定 drag-n-drop 可以开始的时候,调用此方法
    - (NSDraggingSession *)beginDraggingSessionWithItems:(NSArray *)items event:(NSEvent *)event source:(id)source;
    

    调用这个方法之后,系统会自动地更新拖动过程的示意图的位置。当然,你要告诉系统示意图的开始位置和图片,通过上面的方法中的参数。另外,drag-n-drop 通过剪贴板在源和目的之间进行通信,所以剪贴板充当通信信道。下面的代码配置了通信信道和示意图:

    // NSPasteboardItem 用于在 drag-n-drap 的双方之间进行通信
    NSPasteboardItem *pbItem = [NSPasteboardItem new];
    // 你可以直接传输数据, type 可自定义.
    [pbItem setString:@"Transition" forType:NSPasteboardTypeString];
    // 或者指定传输的数据由 NSPasteboardItemDataProvider 提供。
    //[pbItem setDataProvider:self forTypes:@[NSPasteboardTypeString]];
    // NSDraggingItem 用于显示 drag-n-drop 过程的示意图
    NSDraggingItem *dragItem = [[NSDraggingItem alloc] initWithPasteboardWriter:pbItem];
    NSImage *img = [NSImage imageNamed:NSImageNameHomeTemplate];
    NSRect frame = NSMakeRect(0, 0, img.size.width, img.size.height);
    // draggingFrame 用于指定示意图的初始位置(在当前 NSView 中),contents 是示意图(NSImage)
    [dragItem setDraggingFrame:frame contents:img];
    

    这认为用网络通信模型来理解会更好,网络和通信是一个广义的概念,可以解释很多东西,我之前有文章介绍过:http://www.ideawu.net/blog/archives/528.html

    弄明白了上面的之后,你再查看这几个接口就懂了:NSDraggingSource, NSDraggingDestination, NSPasteboardItemDataProvider

    Posted by ideawu at 2019-02-28 19:52:20
  • 2019-02-21

    用 libtool 合并两个静态链接库

    Views: 30046 | 1 Comment
    libtool -static -o new.a old1.a old2.a
    
    Posted by ideawu at 2019-02-21 10:53:26
  • 2019-02-20

    Xcode 静态链接库找不到的问题

    Views: 26067 | No Comments

    如果你的 Xcode 程序编译正常,但执行时遇到类似的报错:

    +[NSCharacterSet SVGWhitespaceCharacterSet]: unrecognized selector sent to class
    

    这种问题是因为静态链接库中的 Objective-C 的 Category 符号没有被链接进你的程序。你可以用下面的命令看看符号有没有被链接:

    nm Cli | grep SVGWhitespaceCharacterSet
    

    网上说你可以在 Other Linker Flags 里增加 -ObjC,但如果你的项目还要用别的非 OC 的库,则会报错,例如你同时使用 C++ 库。正确的做法是,在 Other Linker Flags 里增加

    -force_load $(PROJECT_DIR)/...../libxxx.a
    

    -force_load 后面跟绝对路径。

    Posted by ideawu at 2019-02-20 19:44:17
|<<<111213141516171819>>>| 15/138 Pages, 825 Results.