Какова регулярность позиционирования координат в основной графике в программировании на iphone? - PullRequest
2 голосов
/ 16 ноября 2011

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

self.inverseScale = (CGFloat)1.0/[UIScreen mainScreen].scale;
CGContextRef context=UIGraphicsGetCurrentContext();
CGContextScaleCTM(context, inverseScale, inverseScale);

и после этого определить следующее:

CGContextSetShouldAntialias(context, NO);
CGContextSetLineWidth(context, 1.0);

Хорошо, теперь у меня размер экрана сетчатки 640x960, но я немного запутался в координатах. Я подумал, что если мне нужно нарисовать прямоугольник, мне нужно сделать следующее:

CGContextSetFillColorWithColor(context, someBlackColor);
CGContextFillRect(context, displaySizeRect);
CGContextSetStrokeColorWithColor(context, white);
CGContextAddRect(context, CGRectMake(0, 0, 639, 959));
CGContextStrokePath(context);

Но вместо этого я обнаружил, что мне нужно написать:

CGContextAddRect(context, CGRectMake(0, 1, 639, 959));

вместо:

CGContextAddRect(context, CGRectMake(0, 0, 639, 959));

Почему ???

Мое обращение неверно или что?

Ответы [ 4 ]

6 голосов
/ 16 ноября 2011

РЕДАКТИРОВАНИЕ / ДОПОЛНЕНИЯ ВНУТРИ

Похоже, вы не уверены, как система координат отображает пиксельную сетку.Когда вы рисуете в 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, чтобы проиллюстрировать, как окончания линий связаны с пикселями:

Line endings with hypothetical pixel grids overlaid

Но я отвлекся. Вот пример; Рассмотрим следующий код:

- (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%, я вижу следующее:

Non-alpha-blended 10 device pixel line at 3200%

Теперь, да, в какой-то момент, далеко от точки заботы (для большинства людей), вы можете столкнуться с накопленной ошибкой с плавающей запятой, если вы преобразовываете значения или работаете с длинным конвейером вычислений , Я видел ошибку, столь значительную, как 0,000001, которая всплывает в очень сложных ситуациях, и даже такая ошибка может вас укусить, если вы говорите о таких ситуациях, как разница между 1.999999 и 2.000001, но это НЕ то, что вы видите здесь ,

Если ваша цель состоит только в том, чтобы рисовать точные растровые изображения, состоящие только из выровненных по оси / прямолинейных элементов, есть причина NO отключить сглаживание контекста. При уровнях масштабирования и плотности растровых изображений, о которых мы говорим в этой ситуации, вы должны легко избежать практически всех проблем, вызванных ошибками с плавающей точкой 1e-6 или меньшей величины. (На самом деле в этом случае все, что меньше 1/255 от одного пикселя, не будет иметь никакого влияния на альфа-смешивание в 8-битном контексте, поскольку такая ошибка будет фактически равна нулю при квантовании до 8 бит.)

Позвольте мне воспользоваться этой возможностью, чтобы порекомендовать книгу: Программирование с помощью Quartz: 2D и PDF-графика в Mac OS X Это отличное чтение, в котором подробно рассматриваются все подобные вещи.

2 голосов
/ 16 ноября 2011

Если вы рисуете на экран, вам не нужно настраивать вашу систему координат. В общем, вы можете просто притворяться, что рисуете на экране без сетчатки. За кулисами графическая система настроит преобразование контекста, чтобы учесть масштабный коэффициент 2.0. Если вам нужно оптимизировать сетчатку, вы можете добавить обнаружение в свой код, но, похоже, вам пока не нужно.

Из руководства по рисованию iOS.

Когда вы выполняете какие-либо пользовательские рисунки в своем приложении, в большинстве случаев вам не нужно заботиться о разрешении основного экрана. Собственные технологии рисования автоматически гарантируют, что координаты, которые вы указываете в карте логических координат, корректно соответствуют пикселям на основном экране. Однако иногда вам может понадобиться узнать текущий масштабный коэффициент, чтобы правильно отобразить ваш контент. В таких ситуациях UIKit, Core Animation и другие системные инфраструктуры предоставляют помощь, необходимую для правильного выполнения чертежа.

См. Здесь Поддержка экранов с высоким разрешением

0 голосов
/ 18 ноября 2011

ipmcc спасибо, например!Теперь все ясно!

Но одна нота, там - это немного альфа-смеси, немного.Это скриншот с 3200-кратным увеличением: enter image description here

* Это линия шириной 1 пиксель и длиной 10 пикселов, нарисованная с помощью функции

CGContextAddLineToPoint();

.Только отключение сглаживания решает эту проблему.

0 голосов
/ 16 ноября 2011

Моя цель - нарисовать несколько фигур на дисплее retina , чтобы каждая линия имела ширину 1 и непрозрачность 100% . В этом случае мой метод преобразования записи? Мне также нужно отключить сглаживание, иначе оно не будет иметь ширину 1 пиксель.

И в этом случае в функции:

CGContextMoveToPoint(context, X, Y);

между 2 пикселями по оси X двигатель выберет правильный, а по оси Y - более высокий. Как это:

pixel map

Но в функции:

CGContextFillRect(context, someRect);

Он будет заполняться так, как он отображается на пиксельной сетке (от 1 до 1).

Это правильно?

А для функции:

CGContextAddRect(context, someRect);

Я еще не понял.

Есть ли определенный Apple метод для отображения графики на пикселях, а не на линиях между пикселями?

P.S. Я не могу полагаться на все эти удобные методы и не могу использовать сглаживание, потому что пишу какую-то утилиту, где мне нужно контролировать каждый пиксель.

...