这个代码是在点还是像素级绘制的? 如何绘制视网膜像素?
考虑这个绘制(循环)渐变的令人钦佩的脚本,
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解释了发生了什么。