Wie kann man ein UIImage verkleinern und es gleichzeitig knusprig / scharf machen, anstatt es zu verwischen?


74

Ich muss ein Bild verkleinern, aber auf scharfe Weise. In Photoshop gibt es beispielsweise die Optionen zur Reduzierung der Bildgröße "Bicubic Smoother" (verschwommen) und "Bicubic Sharper".

Ist dieser Bild-Downscaling-Algorithmus Open Source oder irgendwo dokumentiert oder bietet das SDK Methoden an, um dies zu tun?




Siehe diese Frage. stackoverflow.com/questions/2658738/… Die am häufigsten gewählte Antwort ist die einfachste Lösung für dieses Problem, die ich bisher gefunden habe.
Alper_k

Antworten:


127

Die bloße Verwendung imageWithCGImagereicht nicht aus. Es wird skaliert, aber das Ergebnis ist verschwommen und suboptimal, unabhängig davon, ob es vergrößert oder verkleinert wird.

Wenn Sie das Aliasing richtig machen und die "Zacken" loswerden möchten, benötigen Sie Folgendes: http://vocaro.com/trevor/blog/2009/10/12/resize-a-uiimage-the-right- Weg / .

Mein Arbeitstestcode sieht ungefähr so ​​aus. Dies ist Trevors Lösung mit einer kleinen Anpassung, um mit meinen transparenten PNGs zu arbeiten:

- (UIImage *)resizeImage:(UIImage*)image newSize:(CGSize)newSize {
    CGRect newRect = CGRectIntegral(CGRectMake(0, 0, newSize.width, newSize.height));
    CGImageRef imageRef = image.CGImage;

    UIGraphicsBeginImageContextWithOptions(newSize, NO, 0);
    CGContextRef context = UIGraphicsGetCurrentContext();

    // Set the quality level to use when rescaling
    CGContextSetInterpolationQuality(context, kCGInterpolationHigh);
    CGAffineTransform flipVertical = CGAffineTransformMake(1, 0, 0, -1, 0, newSize.height);

    CGContextConcatCTM(context, flipVertical);  
    // Draw into the context; this scales the image
    CGContextDrawImage(context, newRect, imageRef);

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

    CGImageRelease(newImageRef);
    UIGraphicsEndImageContext();    

    return newImage;
}

Vielen Dank! War auf der Suche danach. Dies funktioniert für PNG, umgeht jedoch Trevors transformForOrientation. Ist diese Funktion wirklich notwendig? Ist es nur für JPG mit Orientierungsmetadaten?
Pixelfreak

@pixelfreak Wollen Sie damit sagen, dass dem Code, den ich vorgestellt habe, das Teil fehlt, das für die Arbeit mit JPEGs erforderlich ist, deren Ausrichtung in den Metadaten festgelegt ist? Sie können meinen Code jederzeit bearbeiten, wenn dies nicht zu kompliziert ist. Ich habe gerade herausgerissen, was ich nicht brauchte
Dan Rosenstark

1
In Trevors Code überprüft er imageOrientation(EXIF-Daten, nehme ich an?) und führt je nach Wert eine zusätzliche Transformation durch. Ich bin mir nicht sicher, ob es notwendig ist. Ich werde damit herumspielen.
Pixelfreak

Danke @pixelfreak, das wäre toll. Wie Sie sehen können, habe ich nur versucht, den Code für einen Anwendungsfall einfach zu gestalten. Dann mache ich eine UIImageView, die verschiedene skalierte Versionen (in einem statischen Array) zwischenspeichert, um die Langsamkeit dieser Größenänderungsroutine zu verringern, und die setFrame-Methode unterbricht. Ich bin nicht sicher, ob das von allgemeinem Interesse ist :)
Dan Rosenstark

1
@Yar Das stimmt. Wenn Sie bereits wissen, welchen Hintergrund Sie zB in Ihrer Tabellenzelle haben, können Sie zuerst den Hintergrund zeichnen:[[UIColor whiteColor] set]; UIRectFill(newRect);
Holtwick

19

Für diejenigen, die Swift verwenden, ist hier die akzeptierte Antwort in Swift:

func resizeImage(image: UIImage, newSize: CGSize) -> (UIImage) {
    let newRect = CGRectIntegral(CGRectMake(0,0, newSize.width, newSize.height))
    let imageRef = image.CGImage

    UIGraphicsBeginImageContextWithOptions(newSize, false, 0)
    let context = UIGraphicsGetCurrentContext()

    // Set the quality level to use when rescaling
    CGContextSetInterpolationQuality(context, kCGInterpolationHigh)
    let flipVertical = CGAffineTransformMake(1, 0, 0, -1, 0, newSize.height)

    CGContextConcatCTM(context, flipVertical)
    // Draw into the context; this scales the image
    CGContextDrawImage(context, newRect, imageRef)

    let newImageRef = CGBitmapContextCreateImage(context) as CGImage
    let newImage = UIImage(CGImage: newImageRef)

    // Get the resized image from the context and a UIImage
    UIGraphicsEndImageContext()

    return newImage
}

Cool! dann könnten Sie es auch als Erweiterung tun.
Dan Rosenstark

2
Warum müssen Sie das Bild umdrehen, um die Größe zu ändern (dh warum brauchen Sie CGContextConcatCTM(context, flipVertical)?
Crashalot

5
kCGInterpolationHigh -> CGInterpolation.High in Swift 2 oder Sie erhalten einen Kompilierungsfehler
Crashalot

1
@Crashalot verwenden CGInterpolationQuality.High anstelle von kCGInterpolationHigh für Swift 2.0
Deepak Thakur

Wenn ich dies benutze, verliere ich jedes Mal 40 MB, wenn CGContextDrawImage aufgerufen wird. Hat jemand ein solches Problem gehabt? Dies ist für mich unbrauchbar, da ich versuche, viele Bilder zu verkleinern und sofort keinen Speicher mehr zu haben.
RowanPD

19

Wenn jemand nach einer Swift-Version sucht, ist hier die Swift-Version von @Dan Rosenstarks akzeptierter Antwort:

func resizeImage(image: UIImage, newHeight: CGFloat) -> UIImage {
    let scale = newHeight / image.size.height
    let newWidth = image.size.width * scale
    UIGraphicsBeginImageContext(CGSizeMake(newWidth, newHeight))
    image.drawInRect(CGRectMake(0, 0, newWidth, newHeight))
    let newImage = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()

    return newImage
}

1
danke für die antwort auch wir können sie als erweiterung verwenden öffentliche erweiterung UIImage {func ...}
mert

Schwester Rays Antwort im folgenden Link funktioniert perfekt für mich stackoverflow.com/questions/6052188/…
Deepak Thakur

12

Wenn Sie beim Skalieren das ursprüngliche Seitenverhältnis des Bildes beibehalten, erhalten Sie immer ein scharfes Bild, unabhängig davon, wie stark Sie es verkleinern.

Sie können die folgende Methode zur Skalierung verwenden:

+ (UIImage *)imageWithCGImage:(CGImageRef)imageRef scale:(CGFloat)scale orientation:(UIImageOrientation)orientation

scheint eine Transformation auf die Ansicht anzuwenden, aber die Größe der zugrunde liegenden Daten nicht zu ändern
Cbas

12

Für Swift 3

func resizeImage(image: UIImage, newSize: CGSize) -> (UIImage) {

    let newRect = CGRect(x: 0, y: 0, width: newSize.width, height: newSize.height).integral
    UIGraphicsBeginImageContextWithOptions(newSize, false, 0)
    let context = UIGraphicsGetCurrentContext()

    // Set the quality level to use when rescaling
    context!.interpolationQuality = CGInterpolationQuality.default
    let flipVertical = CGAffineTransform(a: 1, b: 0, c: 0, d: -1, tx: 0, ty: newSize.height)

    context!.concatenate(flipVertical)
    // Draw into the context; this scales the image
    context?.draw(image.cgImage!, in: CGRect(x: 0.0,y: 0.0, width: newRect.width, height: newRect.height))

    let newImageRef = context!.makeImage()! as CGImage
    let newImage = UIImage(cgImage: newImageRef)

    // Get the resized image from the context and a UIImage
    UIGraphicsEndImageContext()

    return newImage
 }

Dies behandelt das Bild als Aspektfüllung. Gibt es eine Möglichkeit, den Aspekt anzupassen? Danke
Coder221

Das funktioniert nicht. Wenn ich drucke newRect.size, erhalte ich die richtigen, verkleinerten Abmessungen. Wenn ich jedoch drucke newImage.size, sehe ich dort die Originalabmessungen von image. Irgendwelche Ideen, warum das so ist? Herausgefunden. UIGraphicsBeginImageContextWithOptions(newSize, false, 0)verhindert jegliche Skalierung. Der Skalierungsparameter ist 0. Wenn Sie UIGraphicsBeginImageContext(newRect.size)stattdessen verwenden, ist alles in Ordnung.
Schnodderbalken

2

@YAR Ihre Lösung funktioniert ordnungsgemäß.

Es gibt nur eine Sache, die nicht meinen Anforderungen entspricht: Die Größe des gesamten Bildes wird geändert. Ich habe eine Methode geschrieben, die es mag photos app on iphone. Dies berechnet die "längere Seite" und schneidet die "Überlagerung" ab, was zu viel besseren Ergebnissen hinsichtlich der Bildqualität führt.

- (UIImage *)resizeImageProportionallyIntoNewSize:(CGSize)newSize;
{
    CGFloat scaleWidth = 1.0f;
    CGFloat scaleHeight = 1.0f;

    if (CGSizeEqualToSize(self.size, newSize) == NO) {

        //calculate "the longer side"
        if(self.size.width > self.size.height) {
            scaleWidth = self.size.width / self.size.height;
        } else {
            scaleHeight = self.size.height / self.size.width;
        }
    }    

    //prepare source and target image
    UIImage *sourceImage = self;
    UIImage *newImage = nil;

    // Now we create a context in newSize and draw the image out of the bounds of the context to get
    // A proportionally scaled image by cutting of the image overlay
    UIGraphicsBeginImageContext(newSize);

    //Center image point so that on each egde is a little cutoff
    CGRect thumbnailRect = CGRectZero;
    thumbnailRect.size.width  = newSize.width * scaleWidth;
    thumbnailRect.size.height = newSize.height * scaleHeight;
    thumbnailRect.origin.x = (int) (newSize.width - thumbnailRect.size.width) * 0.5;
    thumbnailRect.origin.y = (int) (newSize.height - thumbnailRect.size.height) * 0.5;

    [sourceImage drawInRect:thumbnailRect];

    newImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();

    if(newImage == nil) NSLog(@"could not scale image");

    return newImage ;
}

-1

Diese Erweiterung sollte das Bild skalieren und dabei das ursprüngliche Seitenverhältnis beibehalten. Der Rest des Bildes wird zugeschnitten. (Swift 3)

extension UIImage {    
    func thumbnail(ofSize proposedSize: CGSize) -> UIImage? {

        let scale = min(size.width/proposedSize.width, size.height/proposedSize.height)

        let newSize = CGSize(width: size.width/scale, height: size.height/scale)
        let newOrigin = CGPoint(x: (proposedSize.width - newSize.width)/2, y: (proposedSize.height - newSize.height)/2)

        let thumbRect = CGRect(origin: newOrigin, size: newSize).integral

        UIGraphicsBeginImageContextWithOptions(proposedSize, false, 0)

        draw(in: thumbRect)

        let result = UIGraphicsGetImageFromCurrentImageContext()

        UIGraphicsEndImageContext()

        return result
    }
}

-1

Für schnelles 4.2:

extension UIImage {

    func resized(By coefficient:CGFloat) -> UIImage? {

        guard coefficient >= 0 && coefficient <= 1 else {

            print("The coefficient must be a floating point number between 0 and 1")
            return nil
        }

        let newWidth = size.width * coefficient
        let newHeight = size.height * coefficient

        UIGraphicsBeginImageContext(CGSize(width: newWidth, height: newHeight))

        draw(in: CGRect(x: 0, y: 0, width: newWidth, height: newHeight))

        let newImage = UIGraphicsGetImageFromCurrentImageContext()

        UIGraphicsEndImageContext()

        return newImage
    }
}

@ShahbazSaleem Können Sie die Qualitätsprobleme klären? Ich sehe, Sie haben eine Antwort von Jovan Stankovic mit demselben "Grund" abgelehnt.
gabriel_vincent
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.