此代码是在点级别还是像素级别绘制的?如何绘制视网膜像素?
Is this code drawing at the point or pixel level? How to draw retina pixels?
考虑一下这个绘制(圆形)渐变的令人钦佩的脚本,
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;
但是等等 - 这不是按点而不是像素工作吗?
你真的会如何在密集屏幕上绘制物理像素?
是否是以下问题:
Let's say the density is 4 on the device. Draw just as in the above code, but, on a bitmap four times as big, and then put it in the rect?
这看起来很乱 - 但真的是这样吗?
[注意:github 示例中的代码 而不是 以像素为基础计算梯度。 github 示例中的代码以点为基础计算梯度。 -胖子]
代码以像素为单位运行。首先,它用像素颜色数据填充一个简单的光栅位图缓冲区。这显然没有图像比例或像素以外的单位的概念。接下来,它从该缓冲区创建一个 CGImage
(以一种有点奇怪的方式)。 CGImage
除了像素之外,也没有比例或单位的概念。
问题出在绘制 CGImage
的地方。此时是否进行缩放取决于图形上下文及其配置方式。上下文中有一个隐式转换,从用户 space(点,或多或少)转换为设备 space(像素)。
-drawInContext:
方法应该使用 CGContextConvertRectToDeviceSpace()
转换矩形以获得图像的矩形。请注意,未转换的 rect 仍应用于调用 CGContextDrawImage()
.
因此,对于 2x Retina 显示上下文,原始矩形将以磅为单位。比方说 100x200。图像 rect 的大小将加倍以表示像素,200x400。绘制操作会将其绘制到 100x200 矩形,这看起来像是将大的、高度详细的图像缩小,丢失信息。但是,在内部,绘制操作会在进行实际绘制之前将目标矩形缩放到设备 space,并从 200x400 像素的图像中填充 200x400 像素的区域,保留所有细节。
听起来您正在寻找的是 UIScreen 上的比例 属性:
https://developer.apple.com/documentation/uikit/uiscreen/1617836-scale
这使您可以控制坐标系为您提供的每个虚拟像素的像素数。 iOS 设备基本上在非视网膜坐标下工作。老 link 解释这里发生了什么:
http://www.daveoncode.com/2011/10/22/right-uiimage-and-cgimage-pixel-size-retina-display/
不要使用他的宏,因为某些设备现在的比例为 3.0,但 post 解释了发生了什么。
因此,基于 KenThomases 的精彩回答和一天的测试,这正是您在物理像素级别上绘制的方式。我觉得。
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
是绝对关键的。
我尝试了一些 os 版本,不幸的是 contentsScale
的图层默认值似乎是“1”而不是屏幕密度,无论好坏,所以不要忘记设置它!!! (请注意,OS 中的其他系统也会使用它,以了解如何有效地处理您的图层等)
考虑一下这个绘制(圆形)渐变的令人钦佩的脚本,
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;
但是等等 - 这不是按点而不是像素工作吗?
你真的会如何在密集屏幕上绘制物理像素?
是否是以下问题:
Let's say the density is 4 on the device. Draw just as in the above code, but, on a bitmap four times as big, and then put it in the rect?
这看起来很乱 - 但真的是这样吗?
[注意:github 示例中的代码 而不是 以像素为基础计算梯度。 github 示例中的代码以点为基础计算梯度。 -胖子]
代码以像素为单位运行。首先,它用像素颜色数据填充一个简单的光栅位图缓冲区。这显然没有图像比例或像素以外的单位的概念。接下来,它从该缓冲区创建一个 CGImage
(以一种有点奇怪的方式)。 CGImage
除了像素之外,也没有比例或单位的概念。
问题出在绘制 CGImage
的地方。此时是否进行缩放取决于图形上下文及其配置方式。上下文中有一个隐式转换,从用户 space(点,或多或少)转换为设备 space(像素)。
-drawInContext:
方法应该使用 CGContextConvertRectToDeviceSpace()
转换矩形以获得图像的矩形。请注意,未转换的 rect 仍应用于调用 CGContextDrawImage()
.
因此,对于 2x Retina 显示上下文,原始矩形将以磅为单位。比方说 100x200。图像 rect 的大小将加倍以表示像素,200x400。绘制操作会将其绘制到 100x200 矩形,这看起来像是将大的、高度详细的图像缩小,丢失信息。但是,在内部,绘制操作会在进行实际绘制之前将目标矩形缩放到设备 space,并从 200x400 像素的图像中填充 200x400 像素的区域,保留所有细节。
听起来您正在寻找的是 UIScreen 上的比例 属性:
https://developer.apple.com/documentation/uikit/uiscreen/1617836-scale
这使您可以控制坐标系为您提供的每个虚拟像素的像素数。 iOS 设备基本上在非视网膜坐标下工作。老 link 解释这里发生了什么:
http://www.daveoncode.com/2011/10/22/right-uiimage-and-cgimage-pixel-size-retina-display/
不要使用他的宏,因为某些设备现在的比例为 3.0,但 post 解释了发生了什么。
因此,基于 KenThomases 的精彩回答和一天的测试,这正是您在物理像素级别上绘制的方式。我觉得。
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
是绝对关键的。
我尝试了一些 os 版本,不幸的是 contentsScale
的图层默认值似乎是“1”而不是屏幕密度,无论好坏,所以不要忘记设置它!!! (请注意,OS 中的其他系统也会使用它,以了解如何有效地处理您的图层等)