обрезка uiimage

<p>у меня есть некоторый код, который изменяет размер изображения, чтобы я мог получить масштабированный фрагмент центра изображения - я использую его, чтобы взять <code>uiimage</code> и вернуть маленькое квадратное представление изображения, подобное тому, что видно в представлении альбома приложения "фотографии". (я знаю, что мог бы использовать <code>uiimageview</code> и настроить режим кадрирования для достижения тех же результатов, но эти изображения иногда отображаются в <code>uiwebviews</code>).</p><p>я начал замечать некоторые сбои в этом коде, и я немного озадачен. у меня есть две разные теории, и мне интересно, есть ли одна из них на основе.</p><p>теория 1) я достигаю обрезки, рисуя в контексте закадрового изображения моего целевого размера. поскольку мне нужна центральная часть изображения, я установил аргумент <code>cgrect</code>, передаваемый <code>drawinrect</code>, в нечто большее, чем границы моего контекста изображения. я надеялся, что это кошерный, но пытаюсь ли я использовать другие воспоминания, которых я не должен касаться?</p><p>теория 2) я делаю все это в фоновом потоке. я знаю, что есть части uikit, которые ограничены основным потоком. я предполагал/надеялся, что рисование за пределами экрана не было одним из них. я ошибся?</p><p>(о, как я скучаю по методу <code>nsimage's drawinrect:fromrect:operation:fraction:</code>.)</p>

<p>обновление 2014-05-28: я написал это, когда ios 3 или около того была новинкой, я уверен, что к настоящему времени есть лучшие способы сделать это, возможно, встроенные. как уже упоминали многие, этот метод не учитывает ротацию; прочитайте некоторые дополнительные ответы и распространите любовь, чтобы ответы на этот вопрос были полезны для всех.</p><p>оригинальный ответ:</p><p>я собираюсь скопировать/вставить свой ответ на тот же вопрос в другом месте:</p><p>для этого нет простого метода класса, но есть функция, которую вы можете использовать для получения желаемых результатов: <code>cgimagecreatewithimageinrect(cgimageref, cgrect)</code> поможет вам.</p><p>вот краткий пример его использования:</p><pre><code>cgimageref imageref = cgimagecreatewithimageinrect([largeimage cgimage], croprect); // or use the uiimage wherever you like [uiimageview setimage:[uiimage imagewithcgimage:imageref]]; cgimagerelease(imageref); </code></pre>

<p>чтобы обрезать изображения сетчатки, сохраняя тот же масштаб и ориентацию, используйте следующий метод в категории uiimage (ios 4.0 и выше):</p><pre><code>- (uiimage *)crop:(cgrect)rect { if (self.scale > 1.0f) { rect = cgrectmake(rect.origin.x * self.scale, rect.origin.y * self.scale, rect.size.width * self.scale, rect.size.height * self.scale); } cgimageref imageref = cgimagecreatewithimageinrect(self.cgimage, rect); uiimage *result = [uiimage imagewithcgimage:imageref scale:self.scale orientation:self.imageorientation]; cgimagerelease(imageref); return result; } </code></pre>

<p>вы можете создать категорию uiimage и использовать ее там, где вам нужно. основано на ответах hitscans и комментариях ниже. </p><pre><code>@implementation uiimage (crop) - (uiimage *)crop:(cgrect)rect { rect = cgrectmake(rect.origin.x*self.scale, rect.origin.y*self.scale, rect.size.width*self.scale, rect.size.height*self.scale); cgimageref imageref = cgimagecreatewithimageinrect([self cgimage], rect); uiimage *result = [uiimage imagewithcgimage:imageref scale:self.scale orientation:self.imageorientation]; cgimagerelease(imageref); return result; } @end </code></pre><p>вы можете использовать это так:</p><pre><code>uiimage *imagetocrop = <yourimagetocrop>; cgrect croprect = <areayouwanttocrop>; //for example //cgrectmake(0, 40, 320, 100); uiimage *croppedimage = [imagetocrop crop:croprect]; </code></pre>

<h2>версия swift 3</h2><pre><code>func cropimage(imagetocrop:uiimage, torect rect:cgrect) -> uiimage{ let imageref:cgimage = imagetocrop.cgimage!.cropping(to: rect)! let cropped:uiimage = uiimage(cgimage:imageref) return cropped } let imagetop:uiimage = uiimage(named:"one.jpg")! // add validation </code></pre><p><img src="https://i.stack.imgur.com/6jte8.png" alt="enter image description here"></p><p>с помощью этой функции моста <code>cgrectmake</code> -> <code>cgrect</code> (кредиты на <a href="https://stackoverflow.com/a/38335180/1631379">этот ответ</a> ответил <code>@rob mayoff</code>):</p><pre><code> func cgrectmake(\_ x: cgfloat, \_ y: cgfloat, \_ width: cgfloat, \_ height: cgfloat) -> cgrect { return cgrect(x: x, y: y, width: width, height: height) } </code></pre><p>использование:</p><pre><code>if var image:uiimage = uiimage(named:"one.jpg"){ let croppedimage = cropimage(imagetocrop: image, torect: cgrectmake( image.size.width/4, 0, image.size.width/2, image.size.height) ) } </code></pre><p><em>результат:</em></p><p><img src="https://i.stack.imgur.com/gdaws.png" alt="enter image description here"></p>

<p>вот моя реализация обрезки uiimage, которая подчиняется свойству imageorientation. все ориентации были тщательно проверены.</p><pre><code>inline double rad(double deg) { return deg / 180.0 * m\_pi; } uiimage* uiimagecrop(uiimage* img, cgrect rect) { cgaffinetransform recttransform; switch (img.imageorientation) { case uiimageorientationleft: recttransform = cgaffinetransformtranslate(cgaffinetransformmakerotation(rad(90)), 0, -img.size.height); break; case uiimageorientationright: recttransform = cgaffinetransformtranslate(cgaffinetransformmakerotation(rad(-90)), -img.size.width, 0); break; case uiimageorientationdown: recttransform = cgaffinetransformtranslate(cgaffinetransformmakerotation(rad(-180)), -img.size.width, -img.size.height); break; default: recttransform = cgaffinetransformidentity; }; recttransform = cgaffinetransformscale(recttransform, img.scale, img.scale); cgimageref imageref = cgimagecreatewithimageinrect([img cgimage], cgrectapplyaffinetransform(rect, recttransform)); uiimage *result = [uiimage imagewithcgimage:imageref scale:img.scale orientation:img.imageorientation]; cgimagerelease(imageref); return result; } </code></pre>

<p>заголовки: все эти ответы предполагают <code>cgimage</code>- объект изображения.</p><p><code>image.cgimage</code> может вернуть nil, если <code>uiimage</code> поддерживается <code>ciimage</code>, что было бы в случае, если вы создали это изображение, используя <code>cifilter</code>. </p><p>в этом случае вам, возможно, придется нарисовать изображение в новом контексте и вернуть это изображение (<em> slow </em>).</p><pre><code>uiimage* crop(uiimage *image, rect) { uigraphicsbeginimagecontextwithoptions(rect.size, false, [image scale]); [image drawatpoint:cgpointmake(-rect.origin.x, -rect.origin.y)]; cropped\_image = uigraphicsgetimagefromcurrentimagecontext(); uigraphicsendimagecontext(); return cropped\_image; } </code></pre>

<p>ни один из ответов здесь не обрабатывает все проблемы масштаба и вращения на 100% правильно. вот обобщение всего сказанного до сих пор, начиная с ios7/8. он предназначен для включения в качестве метода в категорию на uiimage.</p><pre><code>- (uiimage *)croppedimageinrect:(cgrect)rect { double (^rad)(double) = ^(double deg) { return deg / 180.0 * m\_pi; }; cgaffinetransform recttransform; switch (self.imageorientation) { case uiimageorientationleft: recttransform = cgaffinetransformtranslate(cgaffinetransformmakerotation(rad(90)), 0, -self.size.height); break; case uiimageorientationright: recttransform = cgaffinetransformtranslate(cgaffinetransformmakerotation(rad(-90)), -self.size.width, 0); break; case uiimageorientationdown: recttransform = cgaffinetransformtranslate(cgaffinetransformmakerotation(rad(-180)), -self.size.width, -self.size.height); break; default: recttransform = cgaffinetransformidentity; }; recttransform = cgaffinetransformscale(recttransform, self.scale, self.scale); cgimageref imageref = cgimagecreatewithimageinrect([self cgimage], cgrectapplyaffinetransform(rect, recttransform)); uiimage *result = [uiimage imagewithcgimage:imageref scale:self.scale orientation:self.imageorientation]; cgimagerelease(imageref); return result; } </code></pre>

<pre><code>cgsize size = [originalimage size]; int padding = 20; int picturesize = 300; int startcroppingposition = 100; if (size.height > size.width) { picturesize = size.width - (2.0 * padding); startcroppingposition = (size.height - picturesize) / 2.0; } else { picturesize = size.height - (2.0 * padding); startcroppingposition = (size.width - picturesize) / 2.0; } // wtf: don't forget that the cgimagecreatewithimageinrect believes that // the image is 180 rotated, so x and y are inverted, same for height and width. cgrect croprect = cgrectmake(startcroppingposition, padding, picturesize, picturesize); cgimageref imageref = cgimagecreatewithimageinrect([originalimage cgimage], croprect); uiimage *newimage = [uiimage imagewithcgimage:imageref scale:1.0 orientation:originalimage.imageorientation]; [m\_photoview setimage:newimage]; cgimagerelease(imageref); </code></pre><p>большинство ответов, которые я видел, касается только позиции (0, 0) для (x, y). ok это один случай, но я бы хотел, чтобы моя операция кадрирования была сосредоточена. мне потребовалось некоторое время, чтобы понять, какая строка стоит после комментария wtf.</p><p>давайте возьмем случай изображения, снятого с портретной ориентацией:</p><ol> <li>высота исходного изображения превышает ширину (пока ничего удивительного!)</li> <li>изображение, которое метод cgimagecreatewithimageinrect представляет в своем собственном мире, на самом деле не портрет, а пейзаж (поэтому также, если вы не используете аргумент ориентации в конструкторе imagewithcgimage, он будет отображаться как повернутый на 180 градусов).</li> <li>итак, вы должны представить, что это пейзаж, позиция (0, 0) - верхний правый угол изображения. </li> </ol><p>надеюсь, это имеет смысл! если этого не произойдет, попробуйте другие значения, и вы увидите, что логика инвертируется, когда дело доходит до выбора правильных x, y, width и height для вашего croprect.</p>

<p>быстрое расширение</p><pre><code>extension uiimage { func crop(var rect: cgrect) -> uiimage { rect.origin.x*=self.scale rect.origin.y*=self.scale rect.size.width*=self.scale rect.size.height*=self.scale let imageref = cgimagecreatewithimageinrect(self.cgimage, rect) let image = uiimage(cgimage: imageref, scale: self.scale, orientation: self.imageorientation)! return image } } </code></pre>

<h1>swift3</h1><pre><code>extension uiimage { func crop(rect: cgrect) -> uiimage? { var scaledrect = rect scaledrect.origin.x *= scale scaledrect.origin.y *= scale scaledrect.size.width *= scale scaledrect.size.height *= scale guard let imageref: cgimage = cgimage?.cropping(to: scaledrect) else { return nil } return uiimage(cgimage: imageref, scale: scale, orientation: imageorientation) } } </code></pre>

<p>быстрая версия ответа <code>awolf</code>, которая сработала для меня:</p><pre><code>public extension uiimage { func croppedimage(inrect rect: cgrect) -> uiimage { let rad: (double) -> cgfloat = { deg in return cgfloat(deg / 180.0 * .pi) } var recttransform: cgaffinetransform switch imageorientation { case .left: let rotation = cgaffinetransform(rotationangle: rad(90)) recttransform = rotation.translatedby(x: 0, y: -size.height) case .right: let rotation = cgaffinetransform(rotationangle: rad(-90)) recttransform = rotation.translatedby(x: -size.width, y: 0) case .down: let rotation = cgaffinetransform(rotationangle: rad(-180)) recttransform = rotation.translatedby(x: -size.width, y: -size.height) default: recttransform = .identity } recttransform = recttransform.scaledby(x: scale, y: scale) let transformedrect = rect.applying(recttransform) let imageref = cgimage!.cropping(to: transformedrect)! let result = uiimage(cgimage: imageref, scale: scale, orientation: imageorientation) return result } } </code></pre>

<p>лучшее решение для обрезки uiimage в <strong>swift</strong>, с точки зрения точности, масштабирования пикселей ...:</p><pre><code>private func squarecropimagetosidelength(let sourceimage: uiimage, let sidelength: cgfloat) -> uiimage { // input size comes from image let inputsize: cgsize = sourceimage.size // round up side length to avoid fractional output size let sidelength: cgfloat = ceil(sidelength) // output size has sidelength for both dimensions let outputsize: cgsize = cgsizemake(sidelength, sidelength) // calculate scale so that smaller dimension fits sidelength let scale: cgfloat = max(sidelength / inputsize.width, sidelength / inputsize.height) // scaling the image with this scale results in this output size let scaledinputsize: cgsize = cgsizemake(inputsize.width * scale, inputsize.height * scale) // determine point in center of "canvas" let center: cgpoint = cgpointmake(outputsize.width/2.0, outputsize.height/2.0) // calculate drawing rect relative to output size let outputrect: cgrect = cgrectmake(center.x - scaledinputsize.width/2.0, center.y - scaledinputsize.height/2.0, scaledinputsize.width, scaledinputsize.height) // begin a new bitmap context, scale 0 takes display scale uigraphicsbeginimagecontextwithoptions(outputsize, true, 0) // optional: set the interpolation quality. // for this you need to grab the underlying cgcontext let ctx: cgcontextref = uigraphicsgetcurrentcontext() cgcontextsetinterpolationquality(ctx, kcginterpolationhigh) // draw the source image into the calculated rect sourceimage.drawinrect(outputrect) // create new image from bitmap context let outimage: uiimage = uigraphicsgetimagefromcurrentimagecontext() // clean up uigraphicsendimagecontext() // pass back new image return outimage } </code></pre><p>инструкции, используемые для вызова этой функции:</p><pre><code>let image: uiimage = uiimage(named: "image.jpg")! let squareimage: uiimage = self.squarecropimagetosidelength(image, sidelength: 320) self.myuiimageview.image = squareimage </code></pre><p>примечание: <a href="http://www.cocoanetics.com/2014/07/square-cropping-images/">начальное вдохновение исходного кода, написанное в objective-c, было найдено в блоге "cocoanetics".</a> </p>

<p>выглядит немного странно, но прекрасно работает и учитывает ориентацию изображения:</p><pre><code>var image:uiimage = ... let img = ciimage(image: image)!.imagebycroppingtorect(rect) image = uiimage(ciimage: img, scale: 1, orientation: image.imageorientation) </code></pre>

<pre><code>- (uiimage *)getsubimage:(cgrect) rect{ cgimageref subimageref = cgimagecreatewithimageinrect(self.cgimage, rect); cgrect smallbounds = cgrectmake(rect.origin.x, rect.origin.y, cgimagegetwidth(subimageref), cgimagegetheight(subimageref)); uigraphicsbeginimagecontext(smallbounds.size); cgcontextref context = uigraphicsgetcurrentcontext(); cgcontextdrawimage(context, smallbounds, subimageref); uiimage* smallimg = [uiimage imagewithcgimage:subimageref]; uigraphicsendimagecontext(); return smallimg; } </code></pre>

<p>вот обновленная версия swift 3 на основе <a href="https://stackoverflow.com/a/38060768/235808">лапша</a> ответ</p><pre><code>func cropping(to rect: cgrect) -> uiimage? { if let cgcrop = cgimage?.cropping(to: rect) { return uiimage(cgimage: cgcrop) } else if let cicrop = ciimage?.cropping(to: rect) { return uiimage(ciimage: cicrop) } return nil } </code></pre>

<p>на ios9.2sdk я использую метод ниже, чтобы преобразовать кадр из uiview в uiimage </p><pre><code>-(uiimage *)getneedimagefrom:(uiimage*)image croprect:(cgrect)rect { cgsize cropsize = rect.size; cgfloat widthscale = image.size.width/self.imagevieworiginal.bounds.size.width; cgfloat heightscale = image.size.height/self.imagevieworiginal.bounds.size.height; cropsize = cgsizemake(rect.size.width*widthscale, rect.size.height*heightscale); cgpoint pointcrop = cgpointmake(rect.origin.x*widthscale, rect.origin.y*heightscale); rect = cgrectmake(pointcrop.x, pointcrop.y, cropsize.width, cropsize.height); cgimageref subimage = cgimagecreatewithimageinrect(image.cgimage, rect); uiimage *croppedimage = [uiimage imagewithcgimage:subimage]; cgimagerelease(subimage); return croppedimage; } </code></pre>

<pre><code> (uiimage *)squareimagewithimage:(uiimage *)image scaledtosize:(cgsize)newsize { double ratio; double delta; cgpoint offset; //make a new square size, that is the resized imaged width cgsize sz = cgsizemake(newsize.width, newsize.width); //figure out if the picture is landscape or portrait, then //calculate scale factor and offset if (image.size.width > image.size.height) { ratio = newsize.width / image.size.width; delta = (ratio*image.size.width - ratio*image.size.height); offset = cgpointmake(delta/2, 0); } else { ratio = newsize.width / image.size.height; delta = (ratio*image.size.height - ratio*image.size.width); offset = cgpointmake(0, delta/2); } //make the final clipping rect based on the calculated values cgrect cliprect = cgrectmake(-offset.x, -offset.y, (ratio * image.size.width) + delta, (ratio * image.size.height) + delta); //start a new context, with scale factor 0.0 so retina displays get //high quality image if ([[uiscreen mainscreen] respondstoselector:@selector(scale)]) { uigraphicsbeginimagecontextwithoptions(sz, yes, 0.0); } else { uigraphicsbeginimagecontext(sz); } uirectclip(cliprect); [image drawinrect:cliprect]; uiimage *newimage = uigraphicsgetimagefromcurrentimagecontext(); uigraphicsendimagecontext(); return newimage; } </code></pre>

<p><strong>обновление swift 2.0 (<code>ciimage</code> совместимость)</strong></p><p>расширение от <a href="https://stackoverflow.com/a/25824454/409958">maxim's answer</a> , но работает, если ваше изображение также основано на <code>ciimage</code>.</p><pre><code>public extension uiimage { func imagebycroppingtorect(rect: cgrect) -> uiimage? { if let image = cgimagecreatewithimageinrect(self.cgimage, rect) { return uiimage(cgimage: image) } else if let image = (self.ciimage)?.imagebycroppingtorect(rect) { return uiimage(ciimage: image) } return nil } } </code></pre>

<p>follow answer of @arne . я просто фиксирую функцию категории. положить его в категорию uiimage.</p><pre><code>-(uiimage*)cropimage:(cgrect)rect{ uigraphicsbeginimagecontextwithoptions(rect.size, false, [self scale]); [self drawatpoint:cgpointmake(-rect.origin.x, -rect.origin.y)]; uiimage* cropped\_image = uigraphicsgetimagefromcurrentimagecontext(); uigraphicsendimagecontext(); return cropped\_image; } </code></pre>

<p>я использую метод ниже.</p><pre><code> -(uiimage *)getneedimagefrom:(uiimage*)image croprect:(cgrect)rect { cgsize cropsize = rect.size; cgfloat widthscale = image.size.width/self.imagevieworiginal.bounds.size.width; cgfloat heightscale = image.size.height/self.imagevieworiginal.bounds.size.height; cropsize = cgsizemake(rect.size.width*widthscale, rect.size.height*heightscale); cgpoint pointcrop = cgpointmake(rect.origin.x*widthscale, rect.origin.y*heightscale); rect = cgrectmake(pointcrop.x, pointcrop.y, cropsize.width, cropsize.height); cgimageref subimage = cgimagecreatewithimageinrect(image.cgimage, rect); uiimage *croppedimage = [uiimage imagewithcgimage:subimage]; cgimagerelease(subimage); return croppedimage; </code></pre><p>}</p>

<p>посмотрите на <a href="https://github.com/vvbogdan/bvcropphoto">https://github.com/vvbogdan/bvcropphoto</a> </p><pre> - (uiimage *) croppedimage { cgfloat scale = self.sourceimage.size.width/self.scrollview.contentsize.width; uiimage * finalimage = nil; cgrect targetframe = cgrectmake ((self.scrollview.contentinset.left + self.scrollview.contentoffset.x) * scale, (self.scrollview.contentinset.top + self.scrollview.contentoffset.y) * scale, self.cropsize.width * scale, self.cropsize.height * scale); cgimageref contextimage = cgimagecreatewithimageinrect ([[self imagewithrotation: self.sourceimage] cgimage], targetframe); if (contextimage! = null) { finalimage = [uiimage imagewithcgimage: contextimage масштаб: self.sourceimage.scale ориентация: uiimageorientationup]; cgimagerelease (contextimage); } return finalimage; } - (uiimage *) imagewithrotation: (uiimage *) image { if (image.imageorientation == uiimageorientationup) возвращает изображение; cgaffinetransform transform = cgaffinetransformidentity; switch (image.imageorientation) { case uiimageorientationdown: case uiimageorientationdownmirrored: transform = cgaffinetransformtranslate (transform, image.size.width, image.size.height); transform = cgaffinetransformrotate (transform, m\_pi); перерыв; case uiimageorientationleft: case uiimageorientationleftmirrored: transform = cgaffinetransformtranslate (transform, image.size.width, 0); transform = cgaffinetransformrotate (transform, m\_pi\_2); перерыв; case uiimageorientationright: case uiimageorientationrightmirrored: transform = cgaffinetransformtranslate (transform, 0, image.size.height); transform = cgaffinetransformrotate (transform, -m\_pi\_2); перерыв; case uiimageorientationup: case uiimageorientationupmirrored: перерыв; } switch (image.imageorientation) { case uiimageorientationupmirrored: case uiimageorientationdownmirrored: transform = cgaffinetransformtranslate (transform, image.size.width, 0); transform = cgaffinetransformscale (transform, -1, 1); перерыв; case uiimageorientationleftmirrored: case uiimageorientationrightmirrored: transform = cgaffinetransformtranslate (transform, image.size.height, 0); transform = cgaffinetransformscale (transform, -1, 1); перерыв; case uiimageorientationup: case uiimageorientationdown: case uiimageorientationleft: case uiimageorientationright: перерыв; } // теперь мы рисуем базовый cgimage в новом контексте, применяя преобразование // рассчитано выше . cgcontextref ctx = cgbitmapcontextcreate (null, image.size.width, image.size.height, cgimagegetbitspercomponent (image.cgimage), 0, cgimagegetcolorspace (image.cgimage), cgii cif ); cgcontextconcatctm (ctx, transform); switch (image.imageorientation) { case uiimageorientationleft: case uiimageorientationleftmirrored: case uiimageorientationright: case uiimageorientationrightmirrored: // grr ... cgcontextdrawimage (ctx, cgrectmake (0, 0, image.size.height, image.size.width), image.cgimage); перерыв; дефолт: cgcontextdrawimage (ctx, cgrectmake (0, 0, image.size.width, image.size.height), image.cgimage); перерыв; } // а теперь мы просто создаем новый uiimage из контекста рисования cgimageref cgimg = cgbitmapcontextcreateimage (ctx); uiimage * img = [uiimage imagewithcgimage: cgimg]; cgcontextrelease (ctx); cgimagerelease (cgimg); return img; } </pre>

<p>ниже может помочь фрагмент кода.</p><pre><code>import uikit extension uiimage { func cropimage(torect rect: cgrect) -> uiimage? { if let imageref = self.cgimage?.cropping(to: rect) { return uiimage(cgimage: imageref) } return nil } } </code></pre>

<p>я не был удовлетворен другими решениями, потому что они либо рисуют несколько раз (используя больше энергии, чем необходимо), либо имеют проблемы с ориентацией. вот то, что я использовал для масштабированного квадратного обрезанного изображения из изображения uiimage *.</p><pre><code>cgfloat minimumside = fminf(image.size.width, image.size.height); cgfloat finalsquaresize = 600.; //create new drawing context for right size cgrect rect = cgrectmake(0, 0, finalsquaresize, finalsquaresize); cgfloat scalingratio = 640.0/minimumside; uigraphicsbeginimagecontext(rect.size); //draw [image drawinrect:cgrectmake((minimumside - photo.size.width)*scalingratio/2., (minimumside - photo.size.height)*scalingratio/2., photo.size.width*scalingratio, photo.size.height*scalingratio)]; uiimage *croppedimage = uigraphicsgetimagefromcurrentimagecontext(); uigraphicsendimagecontext(); </code></pre>