изменение размера UIImage без его полной загрузки в память?

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

Есть ли известный способ изменить размер изображения на диске, не загружая его полностью в память через UIImage? В настоящее время я пытаюсь изменить размер, используя категории на UIImage, как подробно описано здесь, но мое приложение вылетает при попытке создать миниатюру очень большого изображения (например, это - предупреждение, огромное изображение).


person kevboh    schedule 02.05.2011    source источник


Ответы (2)


Вы должны взглянуть на CGImageSource в ImageIO.framework, но он доступен только с iOS 4.0.

Быстрый пример:

-(UIImage*)resizeImageToMaxSize:(CGFloat)max path:(NSString*)path
{
    CGImageSourceRef imageSource = CGImageSourceCreateWithURL((CFURLRef)[NSURL fileURLWithPath:path], NULL);
    if (!imageSource)
        return nil;

    CFDictionaryRef options = (CFDictionaryRef)[NSDictionary dictionaryWithObjectsAndKeys:
        (id)kCFBooleanTrue, (id)kCGImageSourceCreateThumbnailWithTransform,
        (id)kCFBooleanTrue, (id)kCGImageSourceCreateThumbnailFromImageIfAbsent,
        (id)@(max),
        (id)kCGImageSourceThumbnailMaxPixelSize,
        nil];
    CGImageRef imgRef = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, options);

    UIImage* scaled = [UIImage imageWithCGImage:imgRef];

    CGImageRelease(imgRef);
    CFRelease(imageSource);

    return scaled;
}
person Nyx0uf    schedule 02.05.2011
comment
как я могу добавить отдельное значение для высоты и ширины? это значения сетчатки? Должен ли я выбирать 2048 или 1024 для экрана Retina iPad? - person Raegtime; 07.10.2014
comment
Вы должны сделать x*[UIScreen mainScreen].scale - person Rick van der Linde; 07.12.2014
comment
При чем здесь @max? - person Reza.Ab; 19.02.2018
comment
@Reza.Ab - Вы, наверное, уже давно поняли, но это значение kCGImageSourceThumbnailMaxPixelSize, то есть «максимальная ширина и высота в пикселях миниатюры». Nyx0uf передает это значение max в качестве параметра методу. А @(...) просто «упаковывает» это в NSNumber. По сути, это синтаксический сахар для преобразования этого CGFloat в NSNumber. (Вы не можете просто вставить примитивные числовые типы в словарь, вы должны обернуть их в объект NSNumber.) - person Rob; 09.03.2019

Согласно этому сеансу Глубокое погружение в память iOS, нам лучше использовать ImageIO для уменьшения изображения.

Плохое использование UIImage уменьшенных изображений.

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

Используйте ImageIO

  • ImageIO может считывать размеры изображений и информацию о метаданных, не загрязняя память.

  • ImageIO может изменять размер изображений только за счет измененного изображения.

Об изображении в памяти

  • Использование памяти связано с размерами изображений, а не с размером файла.
  • UIGraphicsBeginImageContextWithOptions всегда использует формат рендеринга SRGB, который использует 4 байта на пиксель.
  • Изображение имеет load -> decode -> render 3 фазы.
  • UIImage дорого для размера и изменения размера

Для следующего изображения, если вы используете UIGraphicsBeginImageContextWithOptions, нам нужно всего 590 КБ для загрузки изображения, а нам нужно 2048 pixels x 1536 pixels x 4 bytes per pixel = 10 МБ при декодировании введите здесь описание изображения

в то время как UIGraphicsImageRenderer, представленный в iOS 10, автоматически выберет лучший графический формат в iOS12. Это означает, что вы можете сэкономить 75% памяти, заменив UIGraphicsBeginImageContextWithOptions на UIGraphicsImageRenderer, если вам не нужен SRGB.

Это моя статья об образах iOS в памяти

func resize(url: NSURL, maxPixelSize: Int) -> CGImage? {
    let imgSource = CGImageSourceCreateWithURL(url, nil)
    guard let imageSource = imgSource else {
        return nil
    }

    var scaledImage: CGImage?
    let options: [NSString: Any] = [
            // The maximum width and height in pixels of a thumbnail.
            kCGImageSourceThumbnailMaxPixelSize: maxPixelSize,
            kCGImageSourceCreateThumbnailFromImageAlways: true,
            // Should include kCGImageSourceCreateThumbnailWithTransform: true in the options dictionary. Otherwise, the image result will appear rotated when an image is taken from camera in the portrait orientation.
            kCGImageSourceCreateThumbnailWithTransform: true
    ]
    scaledImage = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, options as CFDictionary)

    return scaledImage
}


let filePath = Bundle.main.path(forResource:"large_leaves_70mp", ofType: "jpg")

let url = NSURL(fileURLWithPath: filePath ?? "")

let image = resize(url: url, maxPixelSize: 600)

or

// Downsampling large images for display at smaller size
func downsample(imageAt imageURL: URL, to pointSize: CGSize, scale: CGFloat) -> UIImage {
    let imageSourceOptions = [kCGImageSourceShouldCache: false] as CFDictionary
    let imageSource = CGImageSourceCreateWithURL(imageURL as CFURL, imageSourceOptions)!
    let maxDimensionInPixels = max(pointSize.width, pointSize.height) * scale
    let downsampleOptions =
        [kCGImageSourceCreateThumbnailFromImageAlways: true,
        kCGImageSourceShouldCacheImmediately: true,
        // Should include kCGImageSourceCreateThumbnailWithTransform: true in the options dictionary. Otherwise, the image result will appear rotated when an image is taken from camera in the portrait orientation.
        kCGImageSourceCreateThumbnailWithTransform: true,
        kCGImageSourceThumbnailMaxPixelSize: maxDimensionInPixels] as CFDictionary
    let downsampledImage =
        CGImageSourceCreateThumbnailAtIndex(imageSource, 0, downsampleOptions)!
    return UIImage(cgImage: downsampledImage)
}
person RY_ Zheng    schedule 03.05.2020