как легко изменить размер/оптимизировать размер изображения с ios?

<p>мое приложение загружает набор файлов изображений из сети и сохраняет их на локальном диске iphone. некоторые из этих изображений имеют довольно большой размер (например, ширина более 500 пикселей). поскольку iphone даже не имеет достаточно большого дисплея, чтобы показать изображение в его исходном размере, я планирую изменить размер изображения до чего-то немного меньшего, чтобы сэкономить место/производительность.</p><p>кроме того, некоторые из этих изображений представляют собой jpeg-файлы, и они не сохраняются в качестве обычной настройки качества 60%.</p><p>как изменить размер изображения с помощью iphone sdk и как изменить настройку качества изображения jpeg?</p>

<p>в качестве ответов на <a href="https://stackoverflow.com/questions/603907/uiimage-resize-then-crop">на этот вопрос</a> приведены несколько предложений. я предложил методику, описанную в <a href="http://ofcodeandmen.poltras.com/2008/10/30/undocumented-uiimage-resizing/">в этом посте</a> , с соответствующим кодом:</p><pre><code>+ (uiimage*)imagewithimage:(uiimage*)image scaledtosize:(cgsize)newsize; { uigraphicsbeginimagecontext( newsize ); [image drawinrect:cgrectmake(0,0,newsize.width,newsize.height)]; uiimage* newimage = uigraphicsgetimagefromcurrentimagecontext(); uigraphicsendimagecontext(); return newimage; } </code></pre><p>что касается хранения изображения, самый быстрый формат изображения для iphone - это png, потому что он имеет оптимизации для этого формата. однако, если вы хотите сохранить эти изображения в формате jpeg, вы можете взять свой uiimage и сделать следующее:</p><pre><code>nsdata *dataforjpegfile = uiimagejpegrepresentation(theimage, 0.6); </code></pre><p>это создает экземпляр nsdata, содержащий необработанные байты для изображения jpeg с настройкой качества 60%. содержимое этого экземпляра nsdata затем может быть записано на диск или кэшировано в памяти.</p>

<p>самый простой и простой способ изменить размеры изображений - это</p><pre><code>float actualheight = image.size.height; float actualwidth = image.size.width; float imgratio = actualwidth/actualheight; float maxratio = 320.0/480.0; if(imgratio!=maxratio){ if(imgratio < maxratio){ imgratio = 480.0 / actualheight; actualwidth = imgratio * actualwidth; actualheight = 480.0; } else{ imgratio = 320.0 / actualwidth; actualheight = imgratio * actualheight; actualwidth = 320.0; } } cgrect rect = cgrectmake(0.0, 0.0, actualwidth, actualheight); uigraphicsbeginimagecontext(rect.size); [image drawinrect:rect]; uiimage *img = uigraphicsgetimagefromcurrentimagecontext(); uigraphicsendimagecontext(); </code></pre>

<p>вышеуказанные методы хорошо работают для маленьких изображений, но когда вы пытаетесь изменить размер очень большого изображения, вы быстро исчерпаете память и вылетите из приложения. гораздо лучший способ - использовать <code>cgimagesourcecreatethumbnailatindex</code>, чтобы изменить размер изображения без его полного декодирования. </p><p>если у вас есть путь к изображению, размер которого вы хотите изменить, вы можете использовать это: </p><pre><code>- (void)resizeimageatpath:(nsstring *)imagepath { // create the image source (from path) cgimagesourceref src = cgimagesourcecreatewithurl((\_\_bridge cfurlref) [nsurl fileurlwithpath:imagepath], null); // to create image source from uiimage, use this // nsdata* pngdata = uiimagepngrepresentation(image); // cgimagesourceref src = cgimagesourcecreatewithdata((cfdataref)pngdata, null); // create thumbnail options cfdictionaryref options = (\_\_bridge cfdictionaryref) @{ (id) kcgimagesourcecreatethumbnailwithtransform : @yes, (id) kcgimagesourcecreatethumbnailfromimagealways : @yes, (id) kcgimagesourcethumbnailmaxpixelsize : @(640) }; // generate the thumbnail cgimageref thumbnail = cgimagesourcecreatethumbnailatindex(src, 0, options); cfrelease(src); // write the thumbnail at path cgimagewritetofile(thumbnail, imagepath); } </code></pre><p>подробнее <a href="http://pulkitgoyal.in/resizing-high-resolution-images-on-ios-without-memory-issues/">здесь</a> .</p>

<p>лучший способ масштабирования изображений без потери соотношения сторон (т.е. без растягивания изображения) - использовать этот метод:</p><pre><code>//to scale images without changing aspect ratio + (uiimage *)scaleimage:(uiimage *)image tosize:(cgsize)newsize { float width = newsize.width; float height = newsize.height; uigraphicsbeginimagecontext(newsize); cgrect rect = cgrectmake(0, 0, width, height); float widthratio = image.size.width / width; float heightratio = image.size.height / height; float divisor = widthratio > heightratio ? widthratio : heightratio; width = image.size.width / divisor; height = image.size.height / divisor; rect.size.width = width; rect.size.height = height; //indent in case of width or height difference float offset = (width - height) / 2; if (offset > 0) { rect.origin.y = offset; } else { rect.origin.x = -offset; } [image drawinrect: rect]; uiimage *smallimage = uigraphicsgetimagefromcurrentimagecontext(); uigraphicsendimagecontext(); return smallimage; } </code></pre><p>добавьте этот метод в свой класс utility, чтобы вы могли использовать его в своем проекте и получать к нему доступ следующим образом:</p><pre><code>xyzimageview.image = [utility scaleimage:youruiimage tosize:xyzimageview.frame.size]; </code></pre><p>этот метод заботится о масштабировании, сохраняя соотношение сторон он также добавляет отступы к изображению в случае, если уменьшенное изображение имеет большую ширину, чем высоту (или наоборот).</p>

<p>если у вас есть контроль над сервером, я настоятельно рекомендую изменить размер изображения на стороне сервера с помощью <a href="http://www.imagemagick.org">imagemagik</a> . загрузка больших изображений и изменение их размера на телефон - это пустая трата многих ценных ресурсов - пропускной способности, аккумулятора и памяти. все из которых не хватает на телефонах.</p>

<p>я разработал оптимальное решение для масштабирования изображения в swift.</p><p>вы можете использовать его, чтобы изменить размер изображения, чтобы заполнить, заполнить аспект или соответствовать указанному размеру. </p><p>вы можете выровнять изображение по центру или любому из четырех краев и четырех углов.</p><p>а также вы можете обрезать дополнительное пространство, которое добавляется, если пропорции исходного изображения и целевого размера не равны.</p><pre><code>enum uiimagealignment { case center, left, top, right, bottom, topleft, bottomright, bottomleft, topright } enum uiimagescalemode { case fill, aspectfill, aspectfit(uiimagealignment) } extension uiimage { func scaleimage(width width: cgfloat? = nil, height: cgfloat? = nil, scalemode: uiimagescalemode = .aspectfit(.center), trim: bool = false) -> uiimage { let prewidthscale = width.map { $0 / size.width } let preheightscale = height.map { $0 / size.height } var widthscale = prewidthscale ?? preheightscale ?? 1 var heightscale = preheightscale ?? widthscale switch scalemode { case .aspectfit(\_): let scale = min(widthscale, heightscale) widthscale = scale heightscale = scale case .aspectfill: let scale = max(widthscale, heightscale) widthscale = scale heightscale = scale default: break } let newwidth = size.width * widthscale let newheight = size.height * heightscale let canvaswidth = trim ? newwidth : (width ?? newwidth) let canvasheight = trim ? newheight : (height ?? newheight) uigraphicsbeginimagecontextwithoptions(cgsizemake(canvaswidth, canvasheight), false, 0) var originx: cgfloat = 0 var originy: cgfloat = 0 switch scalemode { case .aspectfit(let alignment): switch alignment { case .center: originx = (canvaswidth - newwidth) / 2 originy = (canvasheight - newheight) / 2 case .top: originx = (canvaswidth - newwidth) / 2 case .left: originy = (canvasheight - newheight) / 2 case .bottom: originx = (canvaswidth - newwidth) / 2 originy = canvasheight - newheight case .right: originx = canvaswidth - newwidth originy = (canvasheight - newheight) / 2 case .topleft: break case .topright: originx = canvaswidth - newwidth case .bottomleft: originy = canvasheight - newheight case .bottomright: originx = canvaswidth - newwidth originy = canvasheight - newheight } default: break } self.drawinrect(cgrectmake(originx, originy, newwidth, newheight)) let image = uigraphicsgetimagefromcurrentimagecontext() uigraphicsendimagecontext() return image } } </code></pre><p>ниже приведены примеры применения этого решения.</p><p>серый прямоугольник - это изображение целевого сайта, будет изменено на . синие круги в голубом прямоугольнике - это изображение (я использовал круги, потому что это легко увидеть, когда оно масштабируется без сохранения аспекта) . светло-оранжевым цветом отмечены области, которые будут быть обрезанным, если вы передадите <code>trim: true</code>.</p><p><strong>соотношение сторон</strong> до и после масштабирования:</p><p> <a href="https://i.stack.imgur.com/n68mt.png"> <img src="https://i.stack.imgur.com/n68mt.png" alt="aspect fit 1 (before)"> </a> <a href="https://i.stack.imgur.com/jtijs.png"> <img src="https://i.stack.imgur.com/jtijs.png" alt="aspect fit 1 (after)"> </a> </p><p>другой пример <strong>aspect fit</strong>:</p><p> <a href="https://i.stack.imgur.com/mgxkm.png"> <img src="https://i.stack.imgur.com/mgxkm.png" alt="aspect fit 2 (before)"> </a> <a href="https://i.stack.imgur.com/bvoiy.png"> <img src="https://i.stack.imgur.com/bvoiy.png" alt="aspect fit 2 (after)"> </a> </p><p><strong>соотношение сторон</strong> с выравниванием по верху:</p><p> <a href="https://i.stack.imgur.com/r4yg9.png"> <img src="https://i.stack.imgur.com/r4yg9.png" alt="aspect fit 3 (before)"> </a> <a href="https://i.stack.imgur.com/rdgjs.png"> <img src="https://i.stack.imgur.com/rdgjs.png" alt="aspect fit 3 (after)"> </a> </p><p><strong>заполнение аспекта</strong>:</p><p> <a href="https://i.stack.imgur.com/syrdw.png"> <img src="https://i.stack.imgur.com/syrdw.png" alt="aspect fill (before)"> </a> <a href="https://i.stack.imgur.com/rbs06.png"> <img src="https://i.stack.imgur.com/rbs06.png" alt="aspect fill (after)"> </a> </p><p><strong>fill</strong>:</p><p> <a href="https://i.stack.imgur.com/mms8s.png"> <img src="https://i.stack.imgur.com/mms8s.png" alt="fill (before)"> </a> <a href="https://i.stack.imgur.com/id96p.png"> <img src="https://i.stack.imgur.com/id96p.png" alt="fill (after)"> </a> </p><p>я использовал масштабирование в моих примерах, потому что это проще для демонстрации, но решение также работает для уменьшения масштаба, как и в вопросе.</p><p>для сжатия jpeg вы должны использовать это:</p><pre><code>let compressionquality: cgfloat = 0.75 // adjust to change jpeg quality if let data = uiimagejpegrepresentation(image, compressionquality) { // ... } </code></pre><p>вы можете проверить мою <a href="https://gist.github.com/mxl/8d3018996468b8c48d57">gist</a> с игровой площадкой xcode.</p>

<p>для swift 3 приведенный ниже код масштабирует изображение, сохраняя соотношение сторон. вы можете прочитать больше о imagecontext в <a href="https://developer.apple.com/library/content/documentation/2ddrawing/conceptual/drawingprintingios/handlingimages/images.html#//apple\_ref/doc/uid/tp40010156-ch13-sw8">документация apple</a> :</p><pre><code>extension uiimage { class func resizeimage(image: uiimage, newheight: cgfloat) -> uiimage { let scale = newheight / image.size.height let newwidth = image.size.width * scale uigraphicsbeginimagecontext(cgsize(width: newwidth, height: newheight)) image.draw(in: cgrect(x: 0, y: 0, width: newwidth, height: newheight)) let newimage = uigraphicsgetimagefromcurrentimagecontext() uigraphicsendimagecontext() return newimage! } } </code></pre><p>чтобы использовать его, вызовите метод <code>resizeimage()</code>:</p><pre><code>uiimage.resizeimage(image: yourimagename, newheight: yourimagenewheight) </code></pre>

<p>вы можете использовать этот код для масштабирования изображения в нужном размере.</p><pre><code>+ (uiimage *)scaleimage:(uiimage *)image tosize:(cgsize)newsize { cgsize actsize = image.size; float scale = actsize.width/actsize.height; if (scale < 1) { newsize.height = newsize.width/scale; } else { newsize.width = newsize.height*scale; } uigraphicsbeginimagecontext(newsize); [image drawinrect:cgrectmake(0, 0, newsize.width, newsize.height)]; uiimage* newimage = uigraphicsgetimagefromcurrentimagecontext(); uigraphicsendimagecontext(); return newimage; } </code></pre>

<p>добавление к множеству ответов здесь, но я выбрал решение, которое изменяет размер файла, а не размеры. </p><p>это уменьшит размеры и качество изображения, пока оно не достигнет заданного вами размера.</p><pre><code>func compressto(tosizeinmb size: double) -> uiimage? { let bytes = size * 1024 * 1024 let sizeinbytes = int(bytes) var needcompress:bool = true var imgdata:data? var compressingvalue:cgfloat = 1.0 while (needcompress) { if let resizedimage = scaleimage(bymultiplicationfactorof: compressingvalue), let data: data = uiimagejpegrepresentation(resizedimage, compressingvalue) { if data.count < sizeinbytes || compressingvalue < 0.1 { needcompress = false imgdata = data } else { compressingvalue -= 0.1 } } } if let data = imgdata { print("finished with compression value of: \(compressingvalue)") return uiimage(data: data) } return nil } private func scaleimage(bymultiplicationfactorof factor: cgfloat) -> uiimage? { let size = cgsize(width: self.size.width*factor, height: self.size.height*factor) uigraphicsbeginimagecontext(size) draw(in: cgrect(x: 0, y: 0, width: size.width, height: size.height)) if let newimage: uiimage = uigraphicsgetimagefromcurrentimagecontext() { uigraphicsendimagecontext() return newimage; } return nil } </code></pre><p>кредит на <a href="https://stackoverflow.com/a/43404939/1532838">масштабирование по размеру ответа</a> </p>

<p>я закончил тем, что использовал технику брэда для создания метода <code>scaletofitwidth</code> в <code>uiimage+extensions</code>, если это кому-нибудь пригодится ...</p><pre><code>-(uiimage *)scaletofitwidth:(cgfloat)width { cgfloat ratio = width / self.size.width; cgfloat height = self.size.height * ratio; nslog(@"w:%f h:%f",width,height); uigraphicsbeginimagecontext(cgsizemake(width, height)); [self drawinrect:cgrectmake(0.0f,0.0f,width,height)]; uiimage *newimage = uigraphicsgetimagefromcurrentimagecontext(); uigraphicsendimagecontext(); return newimage; } </code></pre><p>тогда куда угодно</p><p><code>#import "uiimage+extensions.h"</code></p><p><code>uiimage *newimage = [image scaletofitwidth:100.0f];</code></p><p>также стоит отметить, что вы можете переместить это дальше в класс <code>uiview+extensions</code>, если вы хотите рендерить изображения из uiview</p>

<p>если изображение находится в каталоге документов, добавьте это<strong>url</strong>extension:</p><pre><code>extension url { func compressedimageurl(quality: cgfloat = 0.3) throws -> url? { let imagedata = try data(contentsof: self) debugprint("image file size before compression: \(imagedata.count) bytes") let compressedurl = nsurl.fileurl(withpath: nstemporarydirectory() + nsuuid().uuidstring + ".jpg") guard let actualimage = uiimage(data: imagedata) else { return nil } guard let compressedimagedata = uiimagejpegrepresentation(actualimage, quality) else { return nil } debugprint("image file size after compression: \(compressedimagedata.count) bytes") do { try compressedimagedata.write(to: compressedurl) return compressedurl } catch { return nil } } } </code></pre><p>использование:</p><pre><code>guard let localimageurl = url(string: "< localimagepath.jpg >") else { return } //here you will get url of compressed image guard let compressedimageurl = try localimageurl.compressedimageurl() else { return } debugprint("compressedimageurl: \(compressedimageurl.absolutestring)") </code></pre><p>примечание. - измените <localimagepath.jpg>, указав локальный путь к изображению jpg.</p>

<pre><code>func resizeimage(image: uiimage, newwidth: cgfloat) -> uiimage? { let scale = newwidth / image.size.width let newheight = cgfloat(200.0) uigraphicsbeginimagecontext(cgsize(width: newwidth, height: newheight)) image.draw(in: cgrect(x: 0, y: 0, width: newwidth, height: newheight)) let newimage = uigraphicsgetimagefromcurrentimagecontext() uigraphicsendimagecontext() return newimage } </code></pre>

<p>я просто хотел ответить на этот вопрос для программистов cocoa swift. эта функция возвращает nsimage с новым размером. вы можете использовать эту функцию следующим образом. </p><pre><code> let sizechangedimage = changeimagesize(image, ratio: 2) // changes image size func changeimagesize (image: nsimage, ratio: cgfloat) -> nsimage { // getting the current image size let w = image.size.width let h = image.size.height // calculating new size let w\_new = w / ratio let h\_new = h / ratio // creating size constant let newsize = cgsizemake(w\_new ,h\_new) //creating rect let rect = nsmakerect(0, 0, w\_new, h\_new) // creating a image context with new size let newimage = nsimage.init(size:newsize) newimage.lockfocus() // drawing image with new size in context image.drawinrect(rect) newimage.unlockfocus() return newimage } </code></pre>

<p>проблема, которая может возникнуть на дисплеях сетчатки, заключается в том, что масштаб изображения устанавливается с помощью imagecapture или около того. функции изменения размера выше не изменят это. в этих случаях изменение размера будет работать не правильно.</p><p>в приведенном ниже коде масштаб установлен в 1 (не масштабирован), а возвращаемое изображение имеет ожидаемый размер. это делается в вызове <code>uigraphicsbeginimagecontextwithoptions</code>.</p><pre><code>-(uiimage *)resizeimage :(uiimage *)theimage :(cgsize)thenewsize { uigraphicsbeginimagecontextwithoptions(thenewsize, no, 1.0); [theimage drawinrect:cgrectmake(0, 0, thenewsize.width, thenewsize.height)]; uiimage *newimage = uigraphicsgetimagefromcurrentimagecontext(); uigraphicsendimagecontext(); return newimage; } </code></pre>