две проблемы:
CoreGraphics чертежных функции работают в терминах точек (единицы компоновки экрана, который является постоянным в приблизительном физическом размере во всех устройствах), а не в пикселях. Количество пикселей на точку отличается от устройств с разной шкалой экрана: iPad 2 и iPad mini - это единственные 1x-устройства, поддерживаемые в iOS 7 и более поздних версиях: iPhone 4, iPad 3, iPad mini 2 и более поздние версии - 2x, за исключением iPhone 6/6s/7 Plus, что составляет 3 раза. Поэтому, если вам нужна линейка волос с одним устройством, вам понадобится 0,5-точечная линия на большинстве современных устройств (и 0,3-точечная ширина на iPhone 6 Plus).
Ширина линии центрирована в квадратной области одной точки. Поэтому, если у вас есть линия от (10,0, 10,0) до (10,0, 20,0), она будет находиться между пикселями с x-координатой 10,0 и 9,0 - при визуализации, сглаживание будет затенять как 10,0, так и 9,0 столбцов пикселей при 50% цвета линии, вместо затенения одного столбца на 100%. Чтобы исправить это, вам нужно поместить свою линию так, чтобы она полностью находилась в пикселе. (Пиксель устройство, а не точка макета.)
Таким образом, чтобы получить один пиксел волосяного покрова, вам нужно как уменьшить ширину линии и смещение точки вы рисуете на величину, которая изменяется в зависимости по масштабному коэффициенту экрана. Вот продолжение вашего теста, что делает это:
// pass in the scale of your UIScreen
func drawHairline(in context: CGContext, scale: CGFloat, color: CGColor) {
// pick which row/column of pixels to treat as the "center" of a point
// through which to draw lines -- favor true center for odd scales, or
// offset to the side for even scales so we fall on pixel boundaries
let center: CGFloat
if Int(scale) % 2 == 0 {
center = 1/(scale * 2)
} else {
center = 0
}
let offset = 0.5 - center // use the "center" choice to create an offset
let p1 = CGPoint(x: 50 + offset, y: 50 + offset)
let p2 = CGPoint(x: 50 + offset, y: 75 + offset)
// draw line of minimal stroke width
let width = 1/scale
context.setLineWidth(width)
context.setStrokeColor(color)
context.beginPath()
context.move(to: p1)
context.addLine(to: p2)
context.strokePath()
}
Расчет centerChoice
обобщает вопрос о необходимости выбрать подпункт пикселей нарисовать свою линию дальше. Вы должны провести через центр точки (смещение 0,5), чтобы затенять целый пиксель на 1-кратном дисплее или затенять только средний пиксель точки на 3-кратном дисплее, но на смещении дисплея 2x 0,5 находится между двумя пикселями которые составляют одну точку. Итак, для 2x вы должны выбрать смещение 0,25 или смещение 0,75 - линия center
делает это для четных или нечетных масштабных коэффициентов в целом.
Примечание № 1: Я изменил свой тестовый пример, чтобы нарисовать вертикальную линию, потому что легче увидеть эффект от сглаживания таким образом. Диагональная линия получит некоторое сглаживание, несмотря ни на что, но вертикальная или горизонтальная линия не получит никакого сглаживания, если она имеет правильную ширину и в нужном месте.
Примечание № 2: iPhone 6/6s/7 Plus имеет логическую шкалу 3,0 и физическую шкалу дисплея около 2,61 - вы можете поиграть с screen.scale
по сравнению с screen.nativeScale
, чтобы увидеть, что дает вам лучшие результаты.
Просто любопытно; как вы могли узнать, что это было 2 пикселя? – Unheilig
Возможный дубликат [CGContextSetLineWidth (контекст, 1) - ширина почти почти как минимум 2 пикселя вместо 1] (http://stackoverflow.com/questions/2033321/cgcontextsetlinewidthcontext-1-the-width-is-almost-alwayas -а-наименее-2-пиксельный) – zisoft
Достойный дублирующий вопрос кандидат, но я удивлен, что нет четкого ответа, который является полным и правильным. Вероятно, есть еще один дубликат, который мне не удается найти. – rickster