2013-12-05 2 views
16

Я ищу в UIImage в iOS.Как уменьшить масштаб UIImage в IOS по размеру данных

Я видел другие вопросы ниже и их подход к тому, как уменьшить изображение по размеру. Resizing Images Objective-C
How to resize the image programmatically in objective-c in iphone
The simplest way to resize an UIImage?

Эти вопросы основаны на изменение размера изображения до определенного размера. В моем случае я ищу, чтобы изменить размер/уменьшить масштаб изображения на основе максимального размера.

В качестве примера я хотел бы установить максимальный размер NSData размером 500 КБ. Я знаю, что я могу получить размер изображения, как это: // Проверить размер изображения возвращается

NSData *imageData = UIImageJPEGRepresentation(image, 0.5); 

// Log out the image size 
NSLog(@"%lu KB",(imageData.length/1024)); 

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

Я не уверен, какой лучший подход для этого. В идеале я не хочу масштабировать изображение до определенного размера все время, но только слегка уменьшает изображение каждый раз. Таким образом, у меня может быть самый большой (размер w/h) самого изображения и максимальный размер (байты). Если я немного уменьшусь только за раз, какой был бы лучший способ сделать это?

EDIT Чтобы подтвердить, я ищу, чтобы изменить размер фактического изображения, но изменить размер изображения таким образом, что оно меньше, чем максимальное NSData Length. Например:
-проверить NSData Длина
-Если выше максимума я хочу передать UIImage в метод
-Затем петлю с помощью этого метода несколько повторно проклейки фактический размер изображения каждый раз, когда
-Until него находится под максимальной длиной NSData, а затем вернуть изображение?

+0

Вы делаете это для оптимизации постоянного хранения или пропускной способности сети? (В этом случае игра с параметрами сжатия JPEG прекрасна.) Или вы пытаетесь контролировать, сколько памяти используется при загрузке изображений в объект 'UIImage'? (В этом случае параметры сжатия JPEG не очень актуальны, что приводит к потере качества изображения, не влияя на объем памяти.) – Rob

+1

@Rob. Я делаю это для ограничений на загрузку. Я хотел бы масштабировать размер изображения в отличие от сжатия, чтобы сохранить качество. В настоящий момент использование памяти не является проблемой. – StuartM

+0

Хорошо, тогда вы можете игнорировать мой ответ, но я буду держать его там для будущих читателей. – Rob

ответ

10

Прямо сейчас, у вас есть рутина, которая говорит:

// Check if the image size is too large 
if ((imageData.length/1024) >= 1024) { 

    while ((imageData.length/1024) >= 1024) { 
     NSLog(@"While start - The imagedata size is currently: %f KB",roundf((imageData.length/1024))); 

     // While the imageData is too large scale down the image 

     // Get the current image size 
     CGSize currentSize = CGSizeMake(image.size.width, image.size.height); 

     // Resize the image 
     image = [image resizedImage:CGSizeMake(roundf(((currentSize.width/100)*80)), roundf(((currentSize.height/100)*80))) interpolationQuality:kMESImageQuality]; 

     // Pass the NSData out again 
     imageData = UIImageJPEGRepresentation(image, kMESImageQuality); 

    } 
} 

Я не советовал бы рекурсивно изменение размера изображения. Каждый раз, когда вы изменяете размер, вы теряете некоторое качество (часто проявляющееся как «смягчение» изображения с потерей детализации, с кумулятивными эффектами). Вы всегда хотите вернуться к оригинальному изображению и изменить размер, который все меньше и меньше. (В качестве несовершеннолетнего в сторону, что if заявление является излишним, тоже.)

Я мог бы предложить следующее:

NSData *imageData = UIImageJPEGRepresentation(image, kMESImageQuality); 
double factor  = 1.0; 
double adjustment = 1.0/sqrt(2.0); // or use 0.8 or whatever you want 
CGSize size   = image.size; 
CGSize currentSize = size; 
UIImage *currentImage = image; 

while (imageData.length >= (1024 * 1024)) 
{ 
    factor  *= adjustment; 
    currentSize = CGSizeMake(roundf(size.width * factor), roundf(size.height * factor)); 
    currentImage = [image resizedImage:currentSize interpolationQuality:kMESImageQuality]; 
    imageData = UIImageJPEGRepresentation(currentImage, kMESImageQuality); 
} 

Примечание, я не прикасаясь image, исходное изображение, а назначая currentImage по каждый раз изменяя размер исходного изображения, уменьшая масштаб каждый раз.

Кстати, если вы задаетесь вопросом о моем загадочном 1.0/sqrt(2.0), я пытался нанести компромисс между вашим итеративным 80% фактором и моим желанием одобрить изменение размера в 2 раза, когда я могу (потому что уменьшение сохраняет более резкость когда это делается силой 2). Но используйте любой нужный вам фактор adjustment.

Наконец, если вы делаете это на огромных изображениях, вы можете подумать об использовании блоков @autoreleasepool. Вы хотите профилировать свое приложение в разделе «Распределения в инструментах» и посмотреть, где находится ваша высокая отметка уровня воды, так как при отсутствии пулов автоопределения это может представлять собой довольно агрессивное использование памяти.

+1

Обновлен, чтобы принять подход Robs, который намного лучше, поскольку мы сохраняем исходное изображение в такте во время изменения размера. Затем, как только мы получим приемлемый размер, мы изменим ссылку gatherImage как новый измененный размер изображения. – StuartM

+0

Что такое kMESImageQuality? –

+0

AFAIK, это некоторая случайная константа, определенная StuartM (которую я тогда точно копировал в своем ответе). Вы увидите, что на него ссылаются другие вопросы (см. Http://stackoverflow.com/q/20483242/1271826, например). Здесь это не очень важно. Используйте все, что захотите. Лично, когда я делаю JPEG, я буду использовать значения от 0,7 до 0,9, которые обеспечивают достойное сжатие, но не слишком сильно разрушают качество изображения. И если я не хочу вводить какие-либо потери изображения, я буду использовать PNG-представления (которые предлагают только небольшое сжатие, но без потерь и меньше JPEG с качеством 1.0). – Rob

7

Помимо максимального размера вам также необходимо выбрать минимальный размер, а также принять решение о производительности. Например, вы можете проверить размер UIImageJPEGRepresentation(image, 1.0). Если вы слишком большой, то вы затем проверяете 0.95 или 0.1?

Один из возможных подходов - получить размер UIImageJPEGRepresentation(image, 1.0) и посмотреть, на каком проценте он слишком большой. Например, скажем, это 600 КБ. Затем вы должны вычислить 500.0/600, который составляет примерно 0.83. Тогда сделайте UIImageJPEGRepresentation(image, 0.83). Это не даст ровно 500 КБ, но может быть достаточно близко.

Другой подход заключается в том, чтобы начать с UIImageJPEGRepresentation(image, 1.0). Это слишком большой, а затем UIImageJPEGRepresentation(image, 0.5) Если слишком большой, то перейдите с 0.25, но если слишком маленький, пойдите с 0.75. Продолжайте разделить разницу, пока не получите приемлемый диапазон желаемого размера.

+0

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

+0

Ваш вопрос был неясным.Вы говорили обо всех вопросах повторной калибровки, которые вам не нужны, и основываясь на размере файла. Во всяком случае, это был бы такой же базовый подход. Вместо настройки качества отрегулируйте размер изображения. Начните с 1024x768 (например). Отрегулируйте этот размер в зависимости от того, сколько вам нужно, чтобы уменьшить изображение. – rmaddy

+0

Извинения Я не был ясно объясняю. Я имел в виду, что я не пытаюсь напрямую изменить конкретный размер, однако я хотел бы изменить размер изображения, чтобы повлиять на размер. Поэтому, если изображение имеет размер 1024 КБ (длина NSData), я хочу изменить размер изображения, которое повлияет на размер. В частности, я ищу лучший способ для этого, я не хочу писать кучу операторов if (re-size), а затем другой размер. Но скорее передайте изображение в метод и верните изображение, имеющее соответствующий размер (NSData Length), вы можете помочь с этим подходом? – StuartM

1

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

Интересный вопрос заключается в том, что у вас есть две переменные для воспроизведения, сжатия JPEG и размеров изображения (есть и другие, но я сохраню это просто). Как вы хотите расставить приоритеты друг над другом? Например, я не думаю, что имеет смысл сохранять полное разрешение, абсурдно сжатое изображение (например, коэффициент качества 0,1). Также не имеет смысла сохранять крошечное разрешение, несжатое изображение. Лично я исправлял качество, как было предложено rmaddy, но установил некоторый разумный уровень (например, качество JPEG не меньше, скажем 0,70). В этот момент я мог бы рассмотреть возможность изменения размеров изображения (и это тоже быстро изменит размер файла) и изменить размеры до тех пор, пока итоговый NSData не станет подходящим.

В любом случае, в моем первоначальном ответе я сосредоточился на потреблении памяти в приложении (в отличие от размера файла). Ради потомства, видим, что ответ ниже:


Если вы пытаетесь контролировать, сколько памяти используется при загрузке изображения в UIImage объекты, которые будут использоваться в UIKit объектов, а затем играть с JPEG сжатия воны» t поможет вам, потому что внутреннее представление изображений после их загрузки в объекты UIKit несжато. Таким образом, в этом случае параметры сжатия JPEG не достигают многого (кроме ущерба качеству изображения).

Чтобы проиллюстрировать эту идею, у меня есть изображение 1920 x 1080. У меня есть формат PNG (файл 629kb), сжатый формат JPEG (217kb) и минимально сжатый формат JPEG (1.1mb) , Но, когда я загружаю эти три различных изображения в UIImageView объектов (даже если они имеют очень маленький frame), «Отчисления» инструмент Instrument показывает мне, что они каждый из которых принимает до 7.91mb:

allocations

Этот потому что, когда вы загружаете изображение в представление изображения, внутреннее несжатое представление этих трех изображений составляет четыре байта на пиксель (байт для красного, один для зеленого, один для синего и один для альфа). Таким образом, мои 1920 x 1080 изображений занимают 1920 x 1080 x 4 = 8,249,400 = 7,91 МБ.

Итак, если вы не хотите, чтобы они загружали более 500 килобайт в память при загрузке их в объекты просмотра изображений, это означает, что вы хотите изменить их размер так, чтобы произведение ширины по высоте было равно 128 000 или меньше (т. е. если квадрат, менее 358 х 358 пикселей).

Но если ваша проблема связана с пропускной способностью сети при загрузке изображений или постоянной памяти, тогда продолжайте играть со значениями сжатия JPEG, как это предлагает превосходный ответ rmaddy. Но если вы пытаетесь решить проблемы с потреблением памяти, пока изображения загружаются в объекты UIKit, не делайте акцент на сжатии, но фокусируйтесь на изменении размера изображения.

+0

Также см. Править для ясности, спасибо Rob – StuartM

+0

@StuartM Понял. Интересный вопрос заключается в том, что у вас есть две переменные для воспроизведения, сжатия JPEG и размеров изображения (есть и другие, но я буду держать их простыми). Как вы хотите расставить приоритеты друг над другом? Например, я не думаю, что имеет смысл сохранять полное разрешение, сильно сжатое изображение (например, коэффициент 0,1). Также не имеет смысла сохранять крошечное разрешение, несжатое изображение. Лично я бы нацелил какое-то разумное сжатие, которое не ухудшало изображение слишком сильно (например, 0,75), а затем изменяло размер, пока результирующий 'NSData' не был подходящим размером. – Rob

+0

Я полностью согласен с этим подходом, я бы использовал сжатие 0.75 в качестве базовой точки. В качестве второстепенного вопроса, если я передаю UIImage между ViewControllers, я предполагаю, что это передает изображение полного качества, если только я не должен был фактически передавать указатель NSData? Если я уменьшаю изображение, используя сжатие NSData, это фактически сжимает изображение UIImage? – StuartM

2

Это был мой подход:

// Check if the image size is too large 
if ((imageData.length/1024) >= 1024) { 

    while ((imageData.length/1024) >= 1024) { 
     NSLog(@"While start - The imagedata size is currently: %f KB",roundf((imageData.length/1024))); 

     // While the imageData is too large scale down the image 

     // Get the current image size 
     CGSize currentSize = CGSizeMake(image.size.width, image.size.height); 

     // Resize the image 
     image = [image resizedImage:CGSizeMake(roundf(((currentSize.width/100)*80)), roundf(((currentSize.height/100)*80))) interpolationQuality:kMESImageQuality]; 

     // Pass the NSData out again 
     imageData = UIImageJPEGRepresentation(image, kMESImageQuality); 

    } 
} 

Метод изменения размера изображения следующим образом:

// Returns a rescaled copy of the image, taking into account its orientation 
// The image will be scaled disproportionately if necessary to fit the bounds specified by the parameter 
- (UIImage *)resizedImage:(CGSize)newSize interpolationQuality:(CGInterpolationQuality)quality { 
    BOOL drawTransposed; 

    switch (self.imageOrientation) { 
     case UIImageOrientationLeft: 
     case UIImageOrientationLeftMirrored: 
     case UIImageOrientationRight: 
     case UIImageOrientationRightMirrored: 
      drawTransposed = YES; 
      break; 

     default: 
      drawTransposed = NO; 
    } 

    return [self resizedImage:newSize 
        transform:[self transformForOrientation:newSize] 
       drawTransposed:drawTransposed 
     interpolationQuality:quality]; 
} 

Последовал:

// Returns a copy of the image that has been transformed using the given affine transform and scaled to the new size 
// The new image's orientation will be UIImageOrientationUp, regardless of the current image's orientation 
// If the new size is not integral, it will be rounded up 
- (UIImage *)resizedImage:(CGSize)newSize 
       transform:(CGAffineTransform)transform 
      drawTransposed:(BOOL)transpose 
    interpolationQuality:(CGInterpolationQuality)quality { 
    CGRect newRect = CGRectIntegral(CGRectMake(0, 0, newSize.width, newSize.height)); 
    CGRect transposedRect = CGRectMake(0, 0, newRect.size.height, newRect.size.width); 
    CGImageRef imageRef = self.CGImage; 

    // Build a context that's the same dimensions as the new size 
    CGContextRef bitmap = CGBitmapContextCreate(NULL, 
               newRect.size.width, 
               newRect.size.height, 
               CGImageGetBitsPerComponent(imageRef), 
               0, 
               CGImageGetColorSpace(imageRef), 
               CGImageGetBitmapInfo(imageRef)); 

    // Rotate and/or flip the image if required by its orientation 
    CGContextConcatCTM(bitmap, transform); 

    // Set the quality level to use when rescaling 
    CGContextSetInterpolationQuality(bitmap, quality); 

    // Draw into the context; this scales the image 
    CGContextDrawImage(bitmap, transpose ? transposedRect : newRect, imageRef); 

    // Get the resized image from the context and a UIImage 
    CGImageRef newImageRef = CGBitmapContextCreateImage(bitmap); 
    UIImage *newImage = [UIImage imageWithCGImage:newImageRef]; 

    // Clean up 
    CGContextRelease(bitmap); 
    CGImageRelease(newImageRef); 

    return newImage; 
} 

// Дополнительные методы для справки

// Returns an affine transform that takes into account the image orientation when drawing a scaled image 
- (CGAffineTransform)transformForOrientation:(CGSize)newSize { 
    CGAffineTransform transform = CGAffineTransformIdentity; 

    switch (self.imageOrientation) { 
     case UIImageOrientationDown:   // EXIF = 3 
     case UIImageOrientationDownMirrored: // EXIF = 4 
      transform = CGAffineTransformTranslate(transform, newSize.width, newSize.height); 
      transform = CGAffineTransformRotate(transform, M_PI); 
      break; 

     case UIImageOrientationLeft:   // EXIF = 6 
     case UIImageOrientationLeftMirrored: // EXIF = 5 
      transform = CGAffineTransformTranslate(transform, newSize.width, 0); 
      transform = CGAffineTransformRotate(transform, M_PI_2); 
      break; 

     case UIImageOrientationRight:   // EXIF = 8 
     case UIImageOrientationRightMirrored: // EXIF = 7 
      transform = CGAffineTransformTranslate(transform, 0, newSize.height); 
      transform = CGAffineTransformRotate(transform, -M_PI_2); 
      break; 
     default: 
      break; 
    } 

    switch (self.imageOrientation) { 
     case UIImageOrientationUpMirrored:  // EXIF = 2 
     case UIImageOrientationDownMirrored: // EXIF = 4 
      transform = CGAffineTransformTranslate(transform, newSize.width, 0); 
      transform = CGAffineTransformScale(transform, -1, 1); 
      break; 

     case UIImageOrientationLeftMirrored: // EXIF = 5 
     case UIImageOrientationRightMirrored: // EXIF = 7 
      transform = CGAffineTransformTranslate(transform, newSize.height, 0); 
      transform = CGAffineTransformScale(transform, -1, 1); 
      break; 
     default: 
      break; 
    } 

    return transform; 
} 
+0

@ Rob/@ rmaddy - Это был подход, который я взял в конце.Я использую оператор while, чтобы проверить размер imageData и затем изменить размер, если размер все еще слишком велик. – StuartM

+2

Очень хорошо. Кстати, я бы не рекурсивно изменял размер изображения. Каждый раз, когда вы изменяете размер, вы теряете некоторое качество (часто проявляющееся как «смягчение» изображения с потерей детализации, с кумулятивными эффектами). Вы всегда хотите вернуться к оригинальному изображению и изменить размер, который все меньше и меньше. И, если резкость важна, я лично сделаю первый размер размером 1/2, если все еще слишком большой, то 1/3 размера оригинала, если все еще слишком большой 1/4 оригинала и т. Д. (Полномочия 2 лучше, на самом деле). Но рекурсивное изменение размера изображения может действительно ухудшить резкость. – Rob

+0

@Rob - это отличная точка в вышеизложенном, я постоянно меняю образ, а не сам оригинал. Какая замечательная мысль, я обновлю ... – StuartM

2
-(NSData*)testData 
{ 
    UIImage *imageToUpload=[UIImage imageNamed:@"images/lifestyle2.jpg"]; 
    NSData *imgData=UIImageJPEGRepresentation(imageToUpload,1.0); 
    float compressionRate=10; 
    while (imgData.length>1024) 
    { 
     if (compressionRate>0.5) 
     { 
      compressionRate=compressionRate-0.5; 
      imgData=UIImageJPEGRepresentation(imageToUpload,compressionRate/10); 
     } 
     else 
     { 
      return imgData; 
     } 
    } 
    return imgData; 
} 

Поддерживает качество изображения не менее 1 МБ.

Зов его,

NSData *compressedImageData=[self testData]; 
NSLog(@"%lu KB",compressedImageData.length); 
Смежные вопросы