Как получить 1 пиксельную линию с NSBezierPath?

Я разрабатываю пользовательский элемент управления. Одним из требований является рисование линий. Хотя это работает, я заметил, что мои линии шириной в 1 пиксель на самом деле не выглядят как строки шириной в 1 пиксель - я знаю, они не совсем пиксели, но вы понимаете, что я имею в виду. Они больше похожи на два или три пикселя в ширину. Это становится очень очевидным, когда я рисую пунктирную линию с штрихом в 1 пиксель и зазором в 2 пикселя. Черточки на 1 пиксель на самом деле выглядят как крошечные линии вместо точек.

Я прочитал документацию по рисованию какао и, хотя Apple упоминает метод setLineWidth, изменение ширины линии на значения меньше 1,0 только сделает линию более неопределенной, а не тонкой.

Итак, я подозреваю, что что-то еще влияет на то, как выглядят мои линии.

Есть идеи?

4 ответа

Решение

Траектории Безье рисуются по центру на их пути, поэтому, если вы нарисуете траекторию шириной 1 пиксель вдоль X-координаты, линия на самом деле рисует вдоль Y-координат { -0,5, 0,5 }. Решение обычно состоит в том, чтобы сместить координату на 0,5, чтобы линия не рисуется в границах субпикселя. Вы должны быть в состоянии сместить ограничивающий прямоугольник на 0,5, чтобы получить более четкое поведение при рисовании.

Фрэнсис МакГрю уже дал правильный ответ, но так как я однажды сделал презентацию по этому вопросу, я подумал, что добавлю несколько фотографий.

Проблема здесь в том, что координаты в кварце лежат на пересечениях между пикселями. Это хорошо при заполнении прямоугольника, потому что каждый пиксель, который находится внутри координат, заполняется. Но линии технически (математически!) Невидимы. Чтобы нарисовать их, Кварц должен нарисовать прямоугольник с заданной шириной линии. Этот прямоугольник центрируется по координатам:

Прямоугольник, который вы указываете Quartz рисовать при указании интегральных координат

Поэтому, когда вы просите Quartz обвести прямоугольник с интегральными координатами, возникает проблема, заключающаяся в том, что он может рисовать только целые пиксели. Но здесь вы видите, что у нас есть половина пикселей. Так что он делает это в среднем цвет. Для линии 50% черного (цвет линии) и 50% белого (фон) он просто рисует каждый пиксель серым цветом:

Половина пикселей усредняется при рисовании между пикселями

Вот откуда берутся ваши размытые рисунки. Исправление теперь очевидно: не рисуйте между пикселями, и вы достигнете этого, переместив ваши точки на половину пикселя, чтобы ваша координата была центрирована по желаемому пикселю:

Координаты смещены на 0,5 в нижнем правом углу

Теперь, конечно, просто смещение может быть не тем, что вы хотели. Потому что, если вы сравните заполненный вариант с штрихованным, обводка будет на один пиксель больше в правом нижнем углу. Если вы, например, обрезаете прямоугольник, это обрезает нижнюю правую часть:

Сравнение между смещенным штрихом и без смещенного прямоугольника

Поскольку люди обычно ожидают, что прямоугольник будет штриховать внутри указанного прямоугольника, то, что вы обычно делаете, это то, что вы смещены на 0,5 к центру, поэтому нижний правый угол фактически сдвигается на один пиксель вверх. С другой стороны, многие приложения для рисования смещены на 0,5 от центра, чтобы избежать наложения между границей и заливкой (что может выглядеть странно при рисовании с прозрачностью).

Обратите внимание, что это справедливо только для 1x экранов. Экран 2x Retina на самом деле демонстрирует эту проблему по-разному, потому что каждый из пикселей ниже фактически отрисовывается 4 пикселями Retina, что означает, что они могут фактически рисовать полпикселя. Тем не менее, у вас все еще есть та же проблема, если вы хотите резкую линию 0.5pt. Кроме того, так как Apple может в будущем представить другие экраны Retina, где, например, каждый пиксель состоит из 9 пикселей Retina (3x) или чего-то еще, вам не следует полагаться на это. Вместо этого теперь есть вызовы API для преобразования прямоугольников в "выравнивание по сторонам", что делает это для вас, независимо от того, используете ли вы 1x, 2x или фиктивный 3x.

PS - С тех пор, как я начал писать все это, я разместил это на своем веб-сайте: http://orangejuiceliberationfront.com/are-your-rectangles-blurry-pale-and-have-rounded-corners/ где я обновлю и пересмотрю это описание и добавлю больше изображений.

Ответ (похоронен) в Apple Docs:

"Чтобы избежать сглаживания при рисовании горизонтальной или вертикальной линии шириной в одну точку, если линия имеет нечетное число пикселей по ширине, вы должны сместить позицию на 0,5 пункта по обе стороны от позиции с целым номером"

Скрыто в Руководстве по рисованию и печати для iOS: Концепции рисования в iOS, хотя ничего особенного в текущем стандартном (OS X) Руководстве по рисованию какао нет.

Что касается эффектов вызова setDefaultLineWidth: документы также утверждают, что:

"Ширина 0 интерпретируется как самая тонкая линия, которая может быть визуализирована на конкретном устройстве. Фактическая ширина визуализированной линии может отличаться от указанной ширины на целых 2 пикселя устройства, в зависимости от положения линии относительно пиксельная сетка и текущие настройки сглаживания. На ширину линии также могут влиять коэффициенты масштабирования, указанные в текущей матрице преобразования активного графического контекста."

Я нашел некоторую информацию, предполагающую, что это вызвано антиалиасингом. Временное отключение сглаживания легко:

[[NSGraphicsContext currentContext] setShouldAntialias: NO];

Это дает четкую линию в 1 пиксель. После рисования просто включите его снова.

Я попробовал решение, предложенное Фрэнсисом МакГрю, сместив координату x на 0,5, однако это не имело никакого значения для появления моей линии.

РЕДАКТИРОВАТЬ: Чтобы быть более точным, я изменил координаты х и у индивидуально и вместе со смещением 0,5.

РЕДАКТИРОВАТЬ 2: Я, должно быть, сделал что-то не так, как изменение координат со смещением 0,5 действительно работает. Конечный результат лучше, чем результат, полученный при отключении сглаживания, поэтому я сделаю ответ Фрэнсиса Мсгрю принятым.

Другие вопросы по тегам