Как создать CGBitmapContext, который работает для дисплея Retina и не тратит место на обычный дисплей?

Правда ли, что если это в UIKit, включая drawRect, HD-аспект дисплея Retina обрабатывается автоматически? Значит ли это, что в drawRect текущий графический контекст для представления 1024 x 768 на самом деле является контекстом растрового изображения 2048 x 1536 пикселей?

(Обновление: если я создаю изображение, используя текущий контекст в drawRect, и печатаю его размер:

CGContextRef context = UIGraphicsGetCurrentContext();
CGImageRef image = CGBitmapContextCreateImage(context);
NSLog(@"width of context %i", (int) CGImageGetWidth(image));
NSLog(@"height of context %i", (int) CGImageGetHeight(image));

то на новом айпаде с отключенной строкой состояния печатается 2048 и 1536, а айпад 2 покажет 1024 и 768)

На самом деле мы наслаждаемся роскошью: 1 точка = 4 пикселя, которые автоматически обрабатываются для нас.

Однако, если мы используем CGBitmapContextCreate, то это действительно будут пиксели, а не точки? (по крайней мере, если мы предоставляем буфер данных для этого растрового изображения, размер буфера (количество байтов), очевидно, не для более высокого разрешения, а для стандартного разрешения, и даже если мы передаем NULL в качестве буфера, чтобы CGBitmapContextCreate обрабатывал буфер для нас размер, вероятно, такой же, как если бы мы передавали буфер данных, и это просто стандартное разрешение, а не разрешение Retina).

Мы всегда можем создать разрешение 2048 x 1536 для iPad 1 и iPad 2, а также для нового iPad, но это приведет к пустой трате памяти, мощности процессора и графического процессора, поскольку оно необходимо только для нового iPad.

Итак, нужно ли нам использовать if () { } else { } для создания такого растрового контекста и как мы это делаем? И весь наш код CGContextMoveToPoint должен быть скорректирован для дисплея Retina, чтобы использовать x * 2 и y * 2 по сравнению с дисплеем без сетчатки, использующим только x, y? Это может быть довольно грязным для кода. (или, может быть, мы можем определить локальную переменную scaleFactor и установить для нее значение [[UIScreen mainScreen] scale], чтобы она была равна 1 для стандартного разрешения и 2, если это сетчатка, поэтому наши x и y всегда будут x * scaleFactor, y * scaleFactor, а не просто x и y, когда мы рисуем с помощью CGContextMoveToPoint и др.)

Кажется, что UIGraphicsBeginImageContextWithOptions может создать его для Retina автоматически, если передается масштаб 0,0, но я не думаю, что его можно использовать, если мне нужно создать контекст и сохранить его (и использовать ivar или свойство UIViewController для его хранения ). Если я не выпущу его с помощью UIGraphicsEndImageContext, то он останется в стеке графического контекста, поэтому, похоже, вместо этого я должен использовать CGBitmapContextCreate. (или мы просто оставляем его внизу стека и не беспокоимся об этом?)


person nonopolarity    schedule 03.06.2012    source источник


Ответы (4)


Проведя дополнительные исследования, я нашел следующее решение:

Если вам нужно использовать CGBitmapContextCreate, то есть два шага, которые могут сделать контекст с размером и системой координат, адаптированными к стандартному дисплею или дисплею Retina:

float scaleFactor = [[UIScreen mainScreen] scale];

CGSize size = CGSizeMake(768, 768);

CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();

CGContextRef context = CGBitmapContextCreate(NULL, 
                           size.width * scaleFactor, size.height * scaleFactor, 
                           8, size.width * scaleFactor * 4, colorSpace, 
                           kCGImageAlphaPremultipliedFirst);

CGContextScaleCTM(context, scaleFactor, scaleFactor);

В примере нужно создать область точка 768 x 768, а на новом iPad она будет иметь размер пикселя< 1536 x 1536. /сильный>. На iPad 2 это 768 x 768 пикселей.

Ключевым фактором является то, что CGContextScaleCTM(context, scaleFactor, scaleFactor); используется для настройки системы координат, поэтому любой рисунок с помощью Core Graphics, такой как CGContextMoveToPoint и т. д., будет работать автоматически, независимо от того, стандартное это разрешение или разрешение Retina.


Еще одно замечание: UIGraphicsBeginImageContext(CGSizeMake(300, 300)); создаст пиксель 300 x 300 на дисплее Retina, а UIGraphicsBeginImageContextWithOptions(CGSizeMake(300, 300), NO, 0.0); создаст пиксель<600 x 600<. /strong> на дисплее Retina. 0.0 предназначен для вызова метода, который автоматически задает правильный размер для стандартного дисплея или дисплея Retina.

person nonopolarity    schedule 03.06.2012

Также попробуйте этот:

- (UIImage *)maskImageWithColor:(UIColor *)color
{
    CGRect rect = CGRectMake(0, 0, self.size.width, self.size.height);
    UIGraphicsBeginImageContextWithOptions(rect.size, NO, self.scale);
    CGContextRef c = UIGraphicsGetCurrentContext();
    [self drawInRect:rect];
    CGContextSetFillColorWithColor(c, [color CGColor]);
    CGContextSetBlendMode(c, kCGBlendModeSourceAtop);
    CGContextFillRect(c, rect);
    UIImage *result = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return result;
}
person SarpErdag    schedule 10.02.2013

После запуска нового контекста изображения вы можете получить его с помощью UIGraphicsGetCurrentContext. Затем, если вы хотите сохранить его и повторно использовать после этого, просто сохраните его, как и любой объект CF (и не забудьте освободить его, когда закончите с ним, в соответствии с правила). Вам все еще нужно вызвать UIGraphicsEndImageContext, чтобы вытолкнуть его из стека контекста UIKit, но если вы сохранили контекст, то контекст будет жить после этого, и вы сможете продолжать использовать его, пока не освободите его.

Позже, если вы захотите снова использовать контекст (и еще не освободили его), одним из способов будет вызов UIGraphicsPushContext, который поместит контекст обратно в стек контекста.

Другой способ использования контекста состоит в том, чтобы предположить, что это CGBitmapContext (документы UIKit называют его «контекстом на основе растрового изображения», но не называют CGBitmapContext по имени) и использовать CGBitmapContextCreateImage для захвата нового изображения из контекста после рисования.

Основное отличие состоит в том, что если вы создали контекст с помощью UIGraphicsCreateImageContextWithOptions, UIGraphicsGetImageFromCurrentImageContext возвращает UIImage, чье scale должно соответствовать значению, с которым вы создали контекст. (Я не знаю, сохранится ли это значение масштаба, если вы извлечете контекст, а затем вернете его позже.) CGBitmapContextCreateImage возвращает CGImage, а CGImage знает только пиксели.

Другое отличие состоит в том, что API-интерфейсы рисования UIKit, такие как UIBezierPath, работают с текущим контекстом в стеке контекстов UIKit. Таким образом, если вы не нажимаете контекст, вы можете использовать Quartz API только для рисования в контексте.

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

person Peter Hosey    schedule 03.06.2012
comment
если использовать UIGraphicsBeginImageContextWithOptions и не освобождать его немедленно, то что, если другие контексты помещаются в стек, то можем ли мы UIGraphicsEndImageContext, когда этот контекст не находится на вершине стека? - person nonopolarity; 03.06.2012
comment
и я думаю, вы имеете в виду, что если мы используем CGBitmapContextCreate, то да, мы должны сами вручную обрабатывать разрешение Retina, используя масштабный коэффициент для наших x и y? - person nonopolarity; 03.06.2012
comment
но кажется, что даже если мы используем UIGraphicsCreateImageContextWithOptions, если мы рисуем с помощью CGContextMoveToPoint и т. д., нам все равно приходится вручную обрабатывать x и y с помощью x * scaleFactor и y * scaleFactor - person nonopolarity; 03.06.2012
comment
@ 動靜能量: Рисунок, который использует API-интерфейсы UIKit (или который запрашивает текущий контекст и использует его), перейдет в самый верхний контекст в стеке. Тот факт, что это стек, говорит вам, что произойдет, если будет помещен другой контекст. Что касается необходимости применять коэффициент масштабирования самостоятельно, я не думаю, что это так — весь смысл коэффициента масштабирования заключается в преобразовании координат точек на входе в пиксели на выходе. - person Peter Hosey; 03.06.2012
comment
Вы хотели бы просмотреть мой ответ, чтобы убедиться, что он технически правильный? - person nonopolarity; 03.06.2012

просто создайте контекст с масштабированием 0.0, чтобы получить главный экран с:

UIGraphicsBeginImageContextWithOptions(size,NO,0.0);
CGContextRef context = UIGraphicsGetCurrentContext();

нет третьего шага.

person valexa    schedule 11.02.2013