РЕДАКТИРОВАНИЕ / ДОПОЛНЕНИЯ ВНУТРИ
Похоже, вы не уверены, как система координат отображает пиксельную сетку.Когда вы рисуете в CGContext, вы рисуете в «непрерывную» плоскость с плавающей точкой.Эта непрерывная плоскость отображается на пиксельной сетке дисплея таким образом, что целые значения попадают на линии между пикселями экрана.
Теоретически, в масштабе по умолчанию для любого устройства (т. Е. 1,0 для не сетчатки, 2,0для сетчатки), если вы нарисовали прямоугольник с 0,0 -> 320,480, с черным штрихом с непрозрачностью 100% и шириной 1,0 пт, вы можете ожидать следующих результатов:
- На неСетчатка, у вас будет прямоугольник шириной 1 пиксель вокруг экрана с 50% непрозрачностью.
- На сетчатке у вас будет прямоугольник шириной 1 пиксель вокруг экрана с непрозрачностью 100%.
Это связано с тем, что нулевая линия находится прямо накрай дисплея, между первой строкой / столбцом пикселей на дисплее и первой строкой / столбцом пикселей НЕ на дисплее.Когда вы рисуете линию 1pt в масштабе по умолчанию в этой ситуации, она рисует вдоль центра пути, таким образом, половина ее будет отрисована за пределами экрана, и в вашей непрерывной плоскости у вас будет линия, ширина которой простирается от -0.5pt до 0.5pt.На дисплее без сетчатки это будет линия 1px с непрозрачностью 50%, а на дисплее сетчатки, который будет отображаться как линия 1px с непрозрачностью 100%.
РЕДАКТИРОВАТЬ
OP сказано:
Моя цель - нарисовать фигуры на сетчатке, чтобы каждая линия имела ширину 1 пиксель и непрозрачность 100%.
В этой цели нет ничего, чтотребует, чтобы сглаживание было отключено.Если вы видите альфа-смешивание на горизонтальных и вертикальных линиях с включенным сглаживанием, то вы не рисуете линии в нужных местах или в нужных размерах.
И в этом случае в функции: CGContextMoveToPoint(контекст, X, Y);между 2 пикселями по оси X двигатель выберет правильный, а по оси Y - более высокий.Но в функции: CGContextFillRect (context, someRect);Он будет заполняться так, как будто он отображается на пиксельной сетке (от 1 до 1).
Причина, по которой вы видите это странное поведение, заключается в том, что у вас отключен сглаживание.Главное, что можно увидеть и понять, что здесь происходит, - это оставить сглаживание включенным, а затем внести необходимые изменения, пока вы не сделаете все правильно.Самый простой способ начать делать это - оставить преобразование CGContext без изменений по умолчанию и изменить значения, передаваемые в процедуры рисования.Это правда, что вы можете также выполнить часть этой работы, преобразовав CGContext, но это добавляет математический шаг, который делается для каждой координаты, которую вы передаете любой подпрограмме CG, в которую вы не можете войти в отладчике, поэтому я очень рекомендуюРекомендуется начать со стандартного преобразования и оставить AA включенным.Прежде чем пытаться связываться с преобразованием контекста, вы должны полностью понять, как работает CG-рисование.
Существует ли определенный Apple метод для отображения графики на пикселях, а не на линиях между пикселями?
Одним словом "нет", потому что CGContexts не являются универсальными растровыми изображениями (например, вы можете рисовать в PDF - вы не можете знать, задавая CGContext).Плоскость, в которую вы рисуете, по сути является плоскостью с плавающей точкой.Вот только как это работает.Полностью возможно добиться точного растрового изображения с точностью до пикселя, используя плоскость с плавающей точкой, но для этого вам необходимо понять, как на самом деле работает этот материал.
Вы могли бы быть в состоянии получить загрузку быстрее, взяв заданный CGContext по умолчанию и сделав этот вызов:
CGContextTranslateCTM(context, 0.5, 0.5);
Что это будет делать, это добавлять (0,5, 0,5) к каждой точке, которую вы когда-либо проходите, ко всем последующим вызовам рисования (пока вы не вызовете CGContextRestoreGState).Если вы не вносите никаких других изменений в контекст, это должно сделать его таким, чтобы при рисовании линии от 0,0 до> 10,0, даже при включенном сглаживании, было идеально выровнено по пикселям.(См. Мой первоначальный ответ выше, чтобы понять, почему это так.)
Использование этого трюка 0.5, 0.5 может помочь вам быстрее начать работу, но если вы хотите работать с точным пиксельным изображением компьютерной графики, вам действительно нужноУзнайте, как работают графические контексты с плавающей точкой и как они связаны с растровыми изображениями, которые могут (или не могут) их поддерживать.
Выключение АА, а затем подталкивание значений до тех пор, пока они не станут «правильными», просто вызывает проблемы позже.Например, скажем, однажды UIKit передает вам контекст, который имеет другое отражение в своем преобразовании?В этом случае одно из ваших значений может округляться вниз, где оно использовалось для округления вверх (потому что теперь оно умножается на -1,0, когда применяется переворот).Та же проблема может возникнуть с контекстами, которые были переведены в отрицательный квадрант.Кроме того, вы не знаете (и не можете строго полагаться, от версии к версии), какое правило округления CoreGraphics будет использовать при выключении AA, так что, если по каким-либо причинам вам в конечном итоге будут переданы контексты с разными CTM,получим противоречивые результаты.
Для записи, время, когда вы можете захотеть отключить сглаживание, будет, когда вы рисуете не прямолинейные пути, и вы не хотите, чтобы CoreGraphics альфа смешивал края.Вы можете действительно хотеть этого эффекта, но отключение АА затрудняет изучение, понимание и уверенность в происходящем, поэтому я настоятельно рекомендую оставить его включенным до тех пор, пока вы полностью не разберетесь в этом материале и не получите весь свой прямолинейный рисунок идеально пиксельным.выровнены. Затем переверните переключатель, чтобы избавиться от AA на не прямолинейных путях.
Отключение сглаживания NOT магическим образом разрешает обращение к CGContext в качестве битовой карты.Он принимает плоскость с плавающей запятой и добавляет шаг округления, который вы не можете видеть (с точки зрения кода), не можете контролировать, и который трудно понять и предсказать.В конце концов, у вас все еще есть плоскость с плавающей точкой.
Просто краткий пример, который поможет уточнить:
- (void)drawRect:(CGRect)rect
{
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextSaveGState(ctx);
BOOL doAtDefaultScale = YES;
if (doAtDefaultScale)
{
// Do it by using the right values for the default context
CGContextSetStrokeColorWithColor(ctx, [[UIColor blackColor] CGColor]);
CGContextSetLineWidth(ctx, 0.5); // We're working in scaled pixel. 0.5pt => 1.0px
CGContextStrokeRect(ctx, CGRectMake(25.25, 25.25, 50, 50));
}
else
{
// Do it by transforming the context
CGContextSetStrokeColorWithColor(ctx, [[UIColor blackColor] CGColor]);
CGContextScaleCTM(ctx, 0.5, 0.5); // Back out the default scale
CGContextTranslateCTM(ctx, 0.5, 0.5); // Offset from edges of pixels to centers of pixels
CGContextSetLineWidth(ctx, 1.0); // we're working in device pixels now, having backed out the scale.
CGContextStrokeRect(ctx, CGRectMake(50, 50, 100, 100));
}
CGContextRestoreGState(ctx);
}
Создайте новое приложение с одним представлением, добавьте пользовательский подкласс вида с помощью этогоdrawRect: метод и установите представление по умолчанию в файле .xib для использования вашего пользовательского класса.Обе стороны этого оператора if дают одинаковые результаты на дисплее сетчатки: квадрат устройства размером 100x100 пикселей без альфа-смешивания.Первая сторона делает это, используя «правильные» значения для шкалы по умолчанию.Другая сторона этого делает это, отступая от масштаба 2х, а затем переводя плоскость от выравнивания по краям пикселей устройства к выравниванию с центром пикселей устройства.Обратите внимание, что ширина штрихов различна (масштабные коэффициенты применимы и к ним.) Надеюсь, это поможет.
OP ответил:
Но одна нота, есть некоторая альфа-смесь, немногонемного.Это скриншот с 3200-кратным увеличением:
Нет, правда.Доверьтесь мне.Для этого есть причина, и в контексте NOT сглаживание включено.(Кроме того, я думаю, что вы имеете в виду увеличение 3200%, а не 3200-кратное увеличение - при 3200-кратном увеличении один пиксель не поместится на 30-дюймовом дисплее.)
В приведенном мною примере мы рисовали прямоугольник, поэтому нам не нужно было думать об окончаниях линий, поскольку это замкнутый путь - линия непрерывна. Теперь, когда вы рисуете один сегмент, вам do нужно подумать об окончаниях строк, чтобы избежать альфа-смешения. Это «край пикселя» против «центра пикселя», возвращающегося назад. Стиль ограничения строки по умолчанию - kCGLineCapButt. kCGLineCapButt означает, что конец линии начинается именно там, где вы начинаете рисовать. Если вы хотите, чтобы он вел себя больше как ручка, то есть если вы положите фломастер вниз, намереваясь нарисовать линию на 10 единиц вправо, некоторое количество чернил будет вытекать слева от точная точка, на которую вы указали пером - вы можете использовать kCGLineCapSquare (или kCGLineCapRound для закругленного конца, но для рисования на уровне одного пикселя, которое просто сведет вас с ума с помощью альфа-смешения, так как рассчитает альфа как 1,0 - 0,5 / пи). Я наложил гипотетические пиксельные сетки на эту иллюстрацию из руководства по программированию Apple Quartz 2D, чтобы проиллюстрировать, как окончания линий связаны с пикселями:
Но я отвлекся. Вот пример; Рассмотрим следующий код:
- (void)drawRect:(CGRect)rect
{
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextSaveGState(ctx);
CGContextSetStrokeColorWithColor(ctx, [[UIColor blackColor] CGColor]);
CGContextSetLineWidth(ctx, 0.5); //On device pixel at default scale
CGContextMoveToPoint(ctx, 2.0, 2.25); // Edge of pixel in X, Center of pixel in Y
CGContextAddLineToPoint(ctx, 7.0, 2.25); // Draw a line of 5 scaled, 10 device pixels
CGContextStrokePath(ctx); // Stroke it
CGContextRestoreGState(ctx);
}
Обратите внимание, что вместо того, чтобы перейти к 2.25, 2.25, я перехожу на 2.0, 2.25. Это означает, что я на краю пикселя в измерении X и в центре пикселя в измерении Y. Поэтому я не получу альфа-смешения на концах строки. Действительно, в Acorn, увеличенном до 3200%, я вижу следующее:
Теперь, да, в какой-то момент, далеко от точки заботы (для большинства людей), вы можете столкнуться с накопленной ошибкой с плавающей запятой, если вы преобразовываете значения или работаете с длинным конвейером вычислений , Я видел ошибку, столь значительную, как 0,000001, которая всплывает в очень сложных ситуациях, и даже такая ошибка может вас укусить, если вы говорите о таких ситуациях, как разница между 1.999999 и 2.000001, но это НЕ то, что вы видите здесь ,
Если ваша цель состоит только в том, чтобы рисовать точные растровые изображения, состоящие только из выровненных по оси / прямолинейных элементов, есть причина NO отключить сглаживание контекста. При уровнях масштабирования и плотности растровых изображений, о которых мы говорим в этой ситуации, вы должны легко избежать практически всех проблем, вызванных ошибками с плавающей точкой 1e-6 или меньшей величины. (На самом деле в этом случае все, что меньше 1/255 от одного пикселя, не будет иметь никакого влияния на альфа-смешивание в 8-битном контексте, поскольку такая ошибка будет фактически равна нулю при квантовании до 8 бит.)
Позвольте мне воспользоваться этой возможностью, чтобы порекомендовать книгу: Программирование с помощью Quartz: 2D и PDF-графика в Mac OS X Это отличное чтение, в котором подробно рассматриваются все подобные вещи.