这个代码是在点还是像素级绘制的? 如何绘制视网膜像素?

考虑这个绘制(循环)渐变的令人钦佩的脚本,

https://github.com/paiv/AngleGradientLayer/blob/master/AngleGradient/AngleGradientLayer.m

int w = CGRectGetWidth(rect); int h = CGRectGetHeight(rect); 

接着

 angleGradient(data, w, h .. 

它循环所有这些

 for (int y = 0; y < h; y++) for (int x = 0; x < w; x++) { 

基本上设置颜色

  *p++ = color; 

但等等 – 这不是由点,而不是像素

真的,你会如何画密集屏幕上的物理像素?

这是一个问题:

假设设备上的密度是4。 就像在上面的代码中绘制一样,但是,在一个四倍大的位图上,然后把它放在矩形中?

这似乎是凌乱 – 但是呢?

[注意:github示例中的代码不以像素为单位计算渐变。 github示例中的代码以点为基础计算渐变。 -Fattie]

代码正在像素中工作。 首先,它用像素颜色数据填充一个简单的光栅位图缓冲区。 这显然没有图像比例或像素单位的概念。 接下来,它从该缓冲区创build一个CGImage (有点奇怪)。 CGImage也没有像素以外的比例或单位的概念。

问题出在CGImage绘制的地方。 在这一点上是否进行缩放取决于graphics上下文以及它是如何configuration的。 在用户空间(点,或多或less)转换为设备空间(像素)的上下文中存在隐式转换。

-drawInContext:方法应该使用CGContextConvertRectToDeviceSpace()来转换矩形以得到图像的矩形。 请注意,未转换的矩形仍应该用于对CGContextDrawImage()的调用。

因此,对于2倍的Retina显示上下文,原始的矩形将以点为单位。 比方说100×200。 图像rect将会加倍,代表像素,200×400。 这个绘制操作将把这个绘制到100×200矩形,看起来好像会把大的,高度细节的图像缩小,丢失信息。 然而,在内部,绘制操作将在执行实际绘制之前将目标矩形缩放到设备空间,并从200×400像素图像填充200×400像素区域,保留所有细节。

所以,基于KenThomases的macros伟回答,以及一天的testing,这里就是您如何在物理像素级绘制。 我认为。

 class PixelwiseLayer: CALayer { override init() { super.init() // SET THE CONTENT SCALE AT INITIALIZATION TIME contentsScale = UIScreen.main.scale } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } override open func draw(in ctx: CGContext) { let rectDEVICESPACE = ctx.convertToDeviceSpace(bounds).size // convertToDeviceSpace >>KNOWS ABOUT CONTENT SCALE<< // and YOU have CORRECTLY SET content scale at initialization time // write pixels to DEVICE SPACE, BUT ... let img = pixelByPixelImage(sizeInDeviceSpace: rectDEVICESPACE) // ... BUT the draw# call uses only the NORMAL BOUNDS ctx.draw(img, in: bounds) } private func pixelByPixelImage(sizeInDeviceSpace: CGSize) -> CGImage { let wPIXELS = Int(sizeInDeviceSpace.width) let hPIXELS = Int(sizeInDeviceSpace.height) // !!!THAT IS ACTUAL PIXELS!!! // you !!!DO NOT!!! need to multiply by UIScreen.main.scale, // as is seen in much example code. // convertToDeviceSpace does it properly. let bitsPerComponent: Int = MemoryLayout<UInt8>.size * 8 let bytesPerPixel: Int = bitsPerComponent * 4 / 8 let colorSpace: CGColorSpace = CGColorSpaceCreateDeviceRGB() let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedLast.rawValue) var data = [RGBA]() for y in 0..<hPIXELS { for x in 0..<wPIXELS { let c = yourPixelColor(atPoint: x .. y) data.append(c) } } // the context ... use actual pixels!!!! let ctx = CGContext(data: &data, width: wPIXELS, height: hPIXELS, bitsPerComponent: bitsPerComponent, bytesPerRow: wPIXELS * bytesPerPixel, space: colorSpace, bitmapInfo: bitmapInfo.rawValue) let img = ctx?.makeImage()! return img! // return a CGImage in actual pixels!!!!!! } // (PS, it's very likely you'll want needsDisplayOnBoundsChange as with most layers. // Set it true in init(), needsDisplayOnBoundsChange = true ) } fileprivate struct RGBA { // (build raw data like this) var r: UInt8 var g: UInt8 var b: UInt8 var a: UInt8 } 

关键要素:

先…

  super.init() // SET THE CONTENT SCALE >>>>AT INITIALIZATION TIME<<<< contentsScale = UIScreen.main.scale 

第二…

  override open func draw(in ctx: CGContext) { realPixelSize = ctx.convertToDeviceSpace(bounds).size ... } 

第三…

  override open func draw(in ctx: CGContext) { ... your image = yourPixelDrawingFunction( realPixelSize ) // NOT BOUNDS ctx.draw(img, in: bounds) // NOT REALPIXELSIZE } 

例子…

 console: contentsScale 3.0 UIScreen.main.scale 3.0 bounds (0.0, 0.0, 84.0, 84.0) rectDEVICESPACE (252.0, 252.0) actual pixels being created as data: w, h 252, 252 

在初始化时设置contentsScale是非常重要的。

我尝试了一些操作系统的版本,似乎好或坏contentsScale图层的默认为不幸的是“1”,而不是屏幕密度,所以,不要忘记设置它! (请注意,操作系统中的其他系统也将使用它,也知道如何有效地处理您的图层等)

它听起来像你正在寻找的是在UIScreen的规模属性:

https://developer.apple.com/documentation/uikit/uiscreen/1617836-scale

这允许您控制每个虚拟像素的坐标系统的像素数量。 iOS设备基本上工作在非视网膜坐标。 旧链接解释这里发生了什么:

http://www.daveoncode.com/2011/10/22/right-uiimage-and-cgimage-pixel-size-retina-display/

不要使用他的macros,因为有些设备现在的规模是3.0,但是这个post解释了发生了什么。