Как уменьшить масштаб UImage в IOS по размеру данных
Я искал a UIImage
в iOS
.
Я видел другие вопросы ниже и их подход к тому, как уменьшить размер изображения по размеру.
Изменение размеров изображений Objective-C
Как изменить размер изображения программно в objective-c в iphone
Самый простой способ изменить размер UIImage?
Все эти вопросы основаны на изменении размера изображения до определенного размера. В моем случае я хочу изменить размер/уменьшить масштаб изображения на основе максимального размера.
В качестве примера я хотел бы установить максимальный размер NSData
размером 500 КБ. Я знаю, что я могу получить размер изображения следующим образом://Проверьте размер возвращенного изображения
NSData *imageData = UIImageJPEGRepresentation(image, 0.5);
// Log out the image size
NSLog(@"%lu KB",(imageData.length/1024));
То, что я хотел бы сделать, это какая-то форма цикла здесь. Если размер больше, чем максимальный размер, который я установил, я хотел бы немного уменьшить изображение, а затем проверить размер. Если размер по-прежнему слишком сильно уменьшен, то снова проверьте его, пока он не станет меньше максимального установленного размера.
Я не уверен, какой лучший подход для этого. В идеале я не хочу масштабировать изображение до определенного размера все время, но только слегка уменьшает изображение каждый раз. Таким образом, у меня может быть самый большой (размер w/h) самого изображения и максимальный размер (байты). Если я немного уменьшусь только за раз, что было бы лучшим способом для этого?
ИЗМЕНИТЬ
Чтобы подтвердить, я ищу, чтобы изменить размер фактического изображения, но измените размер изображения так, чтобы оно было меньше максимальной длины NSData
.
Например:
-Проверьте длину NSData
-Если выше максимума я хочу передать UIImage в метод
-После этого метода немного измените размер фактического изображения каждый раз
-Уделяется ли она максимальной длиной NSData
, а затем возвращает изображение?
Ответы
Ответ 1
Сейчас у вас есть рутина, в которой говорится:
// 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
. Вам нужно будет профилировать свое приложение в разделе "Распределение в Инструментах" и посмотреть, где находится ваша отметка о высокой воде, так как при отсутствии пулов автоопределения это может представлять собой довольно агрессивное использование памяти.
Ответ 2
Помимо максимального размера вам также необходимо выбрать минимальный размер, а также принять решение о производительности. Например, вы можете проверить размер 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
. Продолжайте разделить разницу, пока не получите приемлемый диапазон желаемого размера.
Ответ 3
Это был мой подход:
// 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 orientation will be UIImageOrientationUp, regardless of the current image 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 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;
}
Ответ 4
-(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);
Ответ 5
В своем пересмотренном вопросе вы пояснили, что ваша цель заключалась в том, чтобы указать ограничения размера файла при загрузке изображений. В этом случае игра с параметрами сжатия JPEG прекрасна, как было предложено rmaddy.
Интересный вопрос заключается в том, что у вас есть две переменные для воспроизведения, сжатие JPEG и размеры изображения (есть и другие, но я буду держать их простыми). Как вы хотите расставить приоритеты друг над другом? Например, я не думаю, что имеет смысл сохранять полное разрешение, абсурдно сжатое изображение (например, коэффициент качества 0,1). Также не имеет смысла сохранять крошечное разрешение, несжатое изображение. Лично я исправлял качество, как было предложено rmaddy, но установил некоторый разумный уровень (например, качество JPEG не меньше, скажем 0,70). В этот момент я мог бы рассмотреть возможность изменения размеров изображения (и это также очень быстро меняет размер файла) и изменить размеры до тех пор, пока результирующий NSData
не станет подходящим.
В любом случае, в моем первоначальном ответе я сосредоточился на потреблении памяти в приложении (в отличие от размера файла). Для потомков см. Ответ ниже:
Если вы пытаетесь контролировать, сколько памяти используется при загрузке изображений в объекты UIImage
, которые будут использоваться в объектах UIKit
, тогда воспроизведение с сжатием JPEG вам не поможет, поскольку внутреннее представление изображений, когда вы загружаете их в объекты UIKit
, не сжимается. Таким образом, в этом сценарии параметры сжатия JPEG не достигают многого (кроме ущерба качеству изображения).
Чтобы проиллюстрировать эту идею, у меня есть изображение 1920 x 1080. У меня это в формате PNG (файл 629kb), сжатый формат JPEG (217kb) и минимально сжатый формат JPEG (1.1mb). Но когда я загружаю эти три разных изображения в объекты UIImageView
(даже если они имеют очень маленький frame
), инструмент Инструмент "Распределение" показывает мне, что каждый из них занимает 7.91mb:
![allocations]()
Это связано с тем, что при загрузке изображения в представление изображения внутреннее несжатое представление этих трех изображений составляет четыре байта на пиксель (байт для красного, один для зеленого, один для синего и один для альфа). Таким образом, мои 1920 x 1080 изображений занимают 1920 x 1080 x 4 = 8,249,400 = 7.91mb.
Итак, если вы не хотите, чтобы они загружали более 500 кб в память при загрузке их в объекты просмотра изображений, это означает, что вы хотите изменить их размер так, чтобы произведение ширины по высоте составляло 128 000 или меньше (т.е. если квадрат меньше 358 x 358 пикселей).
Но если ваша проблема связана с пропускной способностью сети, когда вы загружаете изображения или постоянную емкость памяти, тогда продолжайте играть и играйте со значениями сжатия JPEG, как это предлагает rmaddy отличный ответ. Но если вы пытаетесь решить проблемы с потреблением памяти, пока изображения загружаются в объекты UIKit, тогда не фокусируйтесь на сжатии, а фокусируйтесь на изменении размера изображения.