Сохранение содержимого UIView в iOS 4 с реальным размером изображений внутри (т.е. масштабирование содержимого для сохранения)

У меня есть UIView со многими UIImageViews в качестве подпредставлений. Приложение работает на iOS4, и я использую изображения с разрешением дисплея Retina (т.е. изображения загружаются с масштабом = 2).

Я хочу сохранить содержимое UIView ... НО ... иметь реальный размер изображений внутри. Т.е. вид имеет размер 200x200 и изображения с масштабом 2 внутри, я хотел бы сохранить результирующее изображение 400x400 и все изображения с их реальным размером.

Теперь первое, что приходит в голову, - это создать новый контекст изображения и снова загрузить все изображения внутри с помощью scale = 1, и это должно сработать, но мне было интересно, есть ли более элегантный способ сделать это? Похоже, что нужно потратить память и процессорное время на то, чтобы перезагрузить все заново, раз уж это уже сделано ...

p.s. если у кого-то есть ответ - включая код было бы неплохо


person Marin Todorov    schedule 18.11.2010    source источник


Ответы (5)


Реализация для рендеринга любого UIView в изображение (работает также и для дисплея Retina).

файл helper.h:

@interface UIView (Ext) 
- (UIImage*) renderToImage;
@end

и принадлежащая реализация в файле helper.m:

#import <QuartzCore/QuartzCore.h>

@implementation UIView (Ext)
- (UIImage*) renderToImage
{
  // IMPORTANT: using weak link on UIKit
  if(UIGraphicsBeginImageContextWithOptions != NULL)
  {
    UIGraphicsBeginImageContextWithOptions(self.frame.size, NO, 0.0);
  } else {
    UIGraphicsBeginImageContext(self.frame.size);
  }

  [self.layer renderInContext:UIGraphicsGetCurrentContext()];
  UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
  UIGraphicsEndImageContext();  
  return image;
}

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

QuartzCore.framework также следует добавить в проект, потому что мы вызываем функцию для объекта слоя.

Чтобы включить слабую ссылку в структуре UIKit, щелкните элемент проекта в левом навигаторе, щелкните цель проекта -> фазы сборки -> связать двоичный файл и выберите «необязательный» (слабый) тип в структуре UIKit.

Вот библиотека с аналогичными расширениями для UIColor, UIImage, NSArray, NSDictionary, ...

person Prcela    schedule 25.11.2010
comment
Какой смысл проверять, можете ли вы использовать UIGraphicsBeginImageContextWithOptions? Это для обратной совместимости? - person pixelfreak; 11.01.2012
comment
Это решение также требует фреймворка QuartzCore для метода renderInContext. - person arlomedia; 16.04.2012
comment
@pixelfreak это потому, что UIGraphicsBeginImageContextWithOptions доступен только в iOS 4+. - person shannoga; 02.07.2012
comment
UIGraphicsBeginImageContextWithOptions (self.frame.size, NO, 0.0); Это работает? думаю 0,0 заменить на 1,0 .. - person Solid Soft; 09.10.2012
comment
Кто-нибудь знает, где находится общая вкладка? Я этого не вижу. UIKit имеет только вариант обязательного или необязательного. Кроме того, мне пришлось отключить ARC для helper.m, но все равно получалась ошибка с не найденным renderInContext. Кроме того, нет реализации / использования, поэтому этот ответ действительно требует некоторого редактирования. Я бы хотел посмотреть, как это работает, если да! - person Jim True; 10.04.2013
comment
Я обновил ответ. Фреймворк QuartzCore должен быть включен в проект. Также я обновил информацию о новом XCode. - person Prcela; 04.07.2013

Я выполнил такую ​​вещь, чтобы сохранить контакты из MKMapView в виде файла PNG (на дисплее сетчатки): MKPinAnnotationView: доступно ли более трех цветов?

Вот отрывок из важной части, которая выполняет сохранение UIView (theView), используя его определение сетчатки:


-(void) saveMyView:(UIView*)theView {
    //The image where the view content is going to be saved.
    UIImage* image = nil;
    UIGraphicsBeginImageContextWithOptions(theView.frame.size, NO, 2.0);
    [theView.layer renderInContext: UIGraphicsGetCurrentContext()];
    image = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    NSData* imgData = UIImagePNGRepresentation(image);
    NSString* targetPath = [NSString stringWithFormat:@"%@/%@", [self writablePath], @"thisismyview.png" ];
    [imgData writeToFile:targetPath atomically:YES]; 
}

-(NSString*) writablePath { NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); NSString *documentsDirectory = [paths objectAtIndex:0]; return documentsDirectory; }

person yonel    schedule 24.11.2010
comment
Привет +1 за упоминание UIGraphicsBeginImageContextWithOptions ... черт возьми, с каждым второстепенным обновлением iOS появляется масса новых случайных функций, подобных этой ... Я устаю :) - person Marin Todorov; 24.11.2010
comment
UIGraphicsBeginImageContextWithOptions для меня тоже новость. Стоит более внимательно ознакомиться с обновлениями ... - person Steven Veltema; 25.11.2010
comment
Это должен быть правильный ответ. Работает отлично! Необходимо включить #import ‹QuartzCore / QuartzCore.h› и добавить фреймворк в проект. - person Jim True; 10.04.2013

Ключевым моментом является то, что третьим параметром UIGraphicsBeginImageContextWithOptions является scale, который определяет, как изображение будет в конечном итоге записано.

Если вам всегда нужны реальные размеры в пикселях, используйте [[UIScreen mainScreen] scale], чтобы получить текущий масштаб экрана:

UIGraphicsBeginImageContextWithOptions(viewSizeInPoints, YES, [[UIScreen mainScreen] scale]);

Если вы используете scale = 1.0 на iPhone4, вы получите изображение с его размером в точках, а результат будет уменьшен по сравнению с истинным количеством пикселей. Если вы вручную записываете изображение в размер 640x960 (например, передавая пиксели в качестве первого параметра), на самом деле это будет уменьшенное изображение, которое масштабируется обратно, что выглядит примерно так же ужасно, как вы себе представляете.

person russbishop    schedule 23.04.2011
comment
Согласно документам, установка аргумента шкалы на 0,0 аналогична установке его на [[UIScreen mainScreen] scale]. - person arlomedia; 16.04.2012

Не могли бы вы просто создать новый графический контекст желаемого размера, использовать CGAffineTransform для его уменьшения, отрендерить корневой слой UIView, восстановить контекст до исходного размера и отрендерить изображение? Не пробовал это для содержимого сетчатки, но похоже, что это хорошо работает для больших изображений, которые были уменьшены в UIImageViews ...

что-то типа:

CGSize originalSize = myOriginalImage.size; //or whatever

//create context
UIGraphicsBeginImageContext(originalSize);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSaveGState(context); //1 original context

// translate/flip the graphics context (for transforming from CG* coords to UI* coords
CGContextTranslateCTM(context, 0, originalSize.height);
CGContextScaleCTM(context, 1.0, -1.0);

//original image
CGContextDrawImage(context, CGRectMake(0,0,originalSize.width,originalSize.height), myOriginalImage.CGImage);

CGContextRestoreGState(context);//1 restore to original for UIView render;

//scaling
CGFloat wratio = originalSize.width/self.view.frame.size.width;
CGFloat hratio = originalSize.height/self.view.frame.size.height;

//scale context to match view size
CGContextSaveGState(context); //1 pre-scaled size
CGContextScaleCTM(context, wratio, hratio);

//render 
[self.view.layer renderInContext:context];

CGContextRestoreGState(context);//1  restore to pre-scaled size;

UIImage *exportImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
person Steven Veltema    schedule 24.11.2010
comment
Привет, Стивен, поправьте меня, если я ошибаюсь, но разве на самом деле это не масштабированный рендеринг (т. Е. Использование 1/4 данных пиксельного изображения), который больше, чем масштабирование, или основная графика работает по-другому? - person Marin Todorov; 24.11.2010
comment
Я могу ошибаться здесь, потому что я немного не уверен в специфике этой части Quartz, но тестирование с приведенным выше показало, что это не уменьшенный рендер. Сначала я специально создал масштабированный контекст растрового изображения, масштабировал и отрендерил его с использованием контекста растрового изображения, но результат был дерьмовым (как и следовало ожидать). Однако вышеупомянутый метод обеспечивает четкое изображение, если исходные источники имеют приемлемый размер / качество. - person Steven Veltema; 25.11.2010
comment
В качестве послесловия этот приведенный выше код предполагает, что myOriginalImage больше, чем размер точки UIView (сетчатка или иначе) - person Steven Veltema; 25.11.2010
comment
Я тоже не король компьютерной графики ... Я попробую ваше решение, действительно интересно, если оно действительно не уменьшено - person Marin Todorov; 25.11.2010

Импортируйте QuartzCore (щелкните основной проект, выберите этапы сборки, импортируйте) и добавьте в нужное место:

#import <QuartzCore/QuartzCore.h>

Мои imageView - это свойства, если у вас нет, игнорируйте .self и передайте imageView в функцию в качестве параметров, затем вызывайте renderInContext для двух изображений в новом UIGraphicsCurrentContext

- (UIImage *)saveImage
{
    UIGraphicsBeginImageContextWithOptions(self.mainImage.bounds.size, NO, 0.0);

    [self.backgroundImage.layer renderInContext:UIGraphicsGetCurrentContext()];
    [self.mainImage.layer renderInContext:UIGraphicsGetCurrentContext()];

    UIImage *savedImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return savedImage;
}
person Joshua Dance    schedule 16.05.2013