Retina ekranda kalite kaybı olmadan UIView'i UIImage'a yakalama


303

Kodum normal cihazlar için iyi çalışıyor ancak retina cihazlarda bulanık görüntüler oluşturuyor.

Sorunum için bir çözüm bilen var mı?

+ (UIImage *) imageWithView:(UIView *)view
{
    UIGraphicsBeginImageContext(view.bounds.size);
    [view.layer renderInContext:UIGraphicsGetCurrentContext()];

    UIImage * img = UIGraphicsGetImageFromCurrentImageContext();

    UIGraphicsEndImageContext();

    return img;
}

bulanık. Bana doğru skala kayboldu ...
Daniel

ben de. aynı sorunla karşılaştı.
RainCast

Sonuç resmini nerede gösteriyorsunuz? Diğerlerinin açıkladığı gibi, görünümü cihazınız 2 veya 3 ölçeğine sahipken, ölçeği 1 olan bir görüntüye dönüştürdüğünüze inanıyorum. Bu bir kalite kaybıdır, ancak bulanık olmamalıdır. Ancak, ölçek 2'ye sahip bir cihazda bu görüntüye tekrar (aynı ekranda mı?) Baktığınızda, ekran görüntüsünde piksel başına 2 piksel kullanarak düşük çözünürlükten daha yükseğe dönüştürülür. Bu ölçeklendirme, büyük olasılıkla enterpolasyon kullanır (iOS'ta varsayılan), bu da ekstra pikseller için renk değerlerinin ortalamasını alır.
Hans Terje Bakke

Yanıtlar:


667

Kullanımından Anahtarı UIGraphicsBeginImageContextiçin UIGraphicsBeginImageContextWithOptions(aynı belgelenmiş bu sayfada ). Ölçek için 0.0 değerini (üçüncü argüman) geçerseniz, ekranın boyutuna eşit bir ölçek faktörüne sahip bir bağlam elde edersiniz.

UIGraphicsBeginImageContextsabit ölçek faktörü 1.0 kullanır, bu nedenle iPhone 4'te diğer iPhone'larda olduğu gibi aynı görüntüyü elde edersiniz. Bahse girerim, iPhone 4, örtük olarak ölçeklendirdiğinizde bir filtre uyguluyor ya da sadece beyniniz, etrafındaki her şeyden daha az keskin olduğunu alıyor.

Yani, sanırım:

#import <QuartzCore/QuartzCore.h>

+ (UIImage *)imageWithView:(UIView *)view
{
    UIGraphicsBeginImageContextWithOptions(view.bounds.size, view.opaque, 0.0);
    [view.layer renderInContext:UIGraphicsGetCurrentContext()];

    UIImage * img = UIGraphicsGetImageFromCurrentImageContext();

    UIGraphicsEndImageContext();

    return img;
}

Ve Swift 4'te:

func image(with view: UIView) -> UIImage? {
    UIGraphicsBeginImageContextWithOptions(view.bounds.size, view.isOpaque, 0.0)
    defer { UIGraphicsEndImageContext() }
    if let context = UIGraphicsGetCurrentContext() {
        view.layer.render(in: context)
        let image = UIGraphicsGetImageFromCurrentImageContext()
        return image
    }
    return nil
}

6
Tommy yanıtı iyi, ancak uyarıyı #import <QuartzCore/QuartzCore.h>kaldırmak için yine de içe aktarmanız gerekiyor renderInContext:.
gwdp

2
@WayneLiu, açıkça bir ölçek faktörü 2.0 belirtebilirsiniz, ancak muhtemelen tam olarak bir iPhone 4 kalitesinde görüntü elde edemezsiniz çünkü yüklü olan bitmapler @ 2x sürümleri yerine standart sürümler olacaktır. Ben şu anda bir UIImageyeniden yüklemek için tutuyor herkese talimat yol yok ama yüksek çözünürlüklü sürümü zorlamak için bir yol olduğunu düşünmüyorum (ve büyük olasılıkla önceden kullanılabilir daha az RAM nedeniyle sorunlara karşı koşmak -retina cihazları zaten).
Tommy

6
0.0fFor scale parametresini kullanmak yerine, kullanımı daha kabul edilebilir [[UIScreen mainScreen] scale]mi, o da işe yarıyor.
Adam Carter

20
@Adam Carter scale: The scale factor to apply to the bitmap. If you specify a value of 0.0, the scale factor is set to the scale factor of the device’s main screen.Açıkça belgelendi, bu yüzden 0.0f bence daha basit ve daha iyi.
cprcrack

5
Bu yanıt iOS 7 için güncel değil. Bunu yapmak için yeni "en iyi" yöntem için cevabım bakın.
Dima

224

Şu anda kabul edilen cevap, en azından iOS 7'yi destekliyorsanız artık güncel değil.

Yalnızca iOS7 + 'yı destekliyorsanız aşağıdakileri kullanmanız gerekir:

+ (UIImage *) imageWithView:(UIView *)view
{
    UIGraphicsBeginImageContextWithOptions(view.bounds.size, view.opaque, 0.0f);
    [view drawViewHierarchyInRect:view.bounds afterScreenUpdates:NO];
    UIImage * snapshotImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return snapshotImage;
}

Hızlı 4:

func imageWithView(view: UIView) -> UIImage? {
    UIGraphicsBeginImageContextWithOptions(view.bounds.size, view.isOpaque, 0.0)
    defer { UIGraphicsEndImageContext() }
    view.drawHierarchy(in: view.bounds, afterScreenUpdates: true)
    return UIGraphicsGetImageFromCurrentImageContext()
}

Gereğince bu makalede , yeni iOS7 yöntemi olduğunu görebilirsiniz drawViewHierarchyInRect:afterScreenUpdates:birçok kez daha hızlıdır renderInContext:.kıyaslama


5
Hiç şüphe yok ki, bu drawViewHierarchyInRect:afterScreenUpdates:çok daha hızlı. Instruments'ta bir Time profiler kullandım. Görüntü üretimim 138ms'den 27ms'ye geçti.
ThomasCle

9
Bazı nedenlerden dolayı, Google Haritalar işaretçileri için özel simgeler oluşturmaya çalışırken bu benim için işe yaramadı; tüm aldığım siyah dikdörtgenlerdi :(
JakeP

6
Eğer ayarlama çalıştı sahip @CarlosP afterScreenUpdates:için YES?
Dima

7
afterScreenUp, YES olarak güncellendi, benim için siyah dikdörtgen sorunu düzeltildi
hyouuu

4
Ayrıca siyah görüntüler alıyordum. Kodu içeri çağırırsam viewDidLoadveya viewWillAppear:görüntüler siyahsa ortaya çıktı; Bunu yapmak zorundaydım viewDidAppear:. Bu yüzden sonunda geri döndüm renderInContext:.
manicaesar

32

@Dima çözümüne dayalı bir Swift uzantısı oluşturdum:

extension UIImage {
    class func imageWithView(view: UIView) -> UIImage {
        UIGraphicsBeginImageContextWithOptions(view.bounds.size, view.opaque, 0.0)
        view.drawViewHierarchyInRect(view.bounds, afterScreenUpdates: true)
        let img = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        return img
    }
}

EDIT: Swift 4 gelişmiş sürüm

extension UIImage {
    class func imageWithView(_ view: UIView) -> UIImage {
        UIGraphicsBeginImageContextWithOptions(view.bounds.size, view.isOpaque, 0)
        defer { UIGraphicsEndImageContext() }
        view.drawHierarchy(in: view.bounds, afterScreenUpdates: true)
        return UIGraphicsGetImageFromCurrentImageContext() ?? UIImage()
    }
}

Kullanımı:

let view = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))  
let image = UIImage.imageWithView(view)

2
Fikir için teşekkürler! Bir kenara olduğu gibi, bağlama başladıktan hemen sonra {UIGraphicsEndImageContext ()} erteleyebilir ve yerel değişkeni img;) kullanmaktan kaçınabilirsiniz.)
Dennis L

İyi ayrıntılı açıklama ve kullanım için çok teşekkürler!
Zeus

Bu neden classbir görüş alan bir işlev?
Rudolf Adamkovič

Bu bir staticişlev gibi çalışır, ancak geçersiz kılmaya gerek olmadığından, bunu staticyerine bir işlev olarak değiştirebilirsiniz class.
Heberti Almeida

25

iOS hızlı

Modern UIGraphicsImageRenderer'ı kullanma

public extension UIView {
    @available(iOS 10.0, *)
    public func renderToImage(afterScreenUpdates: Bool = false) -> UIImage {
        let rendererFormat = UIGraphicsImageRendererFormat.default()
        rendererFormat.opaque = isOpaque
        let renderer = UIGraphicsImageRenderer(size: bounds.size, format: rendererFormat)

        let snapshotImage = renderer.image { _ in
            drawHierarchy(in: bounds, afterScreenUpdates: afterScreenUpdates)
        }
        return snapshotImage
    }
}

@ masaldana2 henüz olmayabilir, ancak bir şeyleri kullanımdan kaldırmaya veya donanım hızlandırmalı oluşturma veya iyileştirmeler eklemeye başlar başlamaz dev omuzlarda olmalısınız (son Apple API'lerini kullanan AKA)
Juan Boero

Performansın renderereski ile karşılaştırılmasının ne olduğunu bilen var mı drawHierarchy..?
Vive

24

@Tommy ve @Dima tarafından verilen cevapları iyileştirmek için UIView'i şeffaf arka planlı ve kalite kaybı olmadan UIiewge'e dönüştürmek için aşağıdaki kategoriyi kullanın . İOS7 üzerinde çalışıyor. (Ya da selfreferansı resminizle değiştirerek bu yöntemi uygulamada yeniden kullanın )

UIView + RenderViewToImage.h

#import <UIKit/UIKit.h>

@interface UIView (RenderToImage)

- (UIImage *)imageByRenderingView;

@end

UIView + RenderViewToImage.m

#import "UIView+RenderViewToImage.h"

@implementation UIView (RenderViewToImage)

- (UIImage *)imageByRenderingView
{
    UIGraphicsBeginImageContextWithOptions(self.bounds.size, NO, 0.0);
    [self drawViewHierarchyInRect:self.bounds afterScreenUpdates:YES];
    UIImage * snapshotImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return snapshotImage;
}

@end

1
AfterScreenUpdates ile beraber drawViewHierarchyInRect kullanırsam: EVET görüntü en boy oranım değişti ve görüntü bozuluyor.
yapılandırma dosyasındaki

Uiview içeriğiniz değiştirildiğinde bu olur mu? Evetse, bu UIImage'ı tekrar oluşturmayı deneyin. Üzgünüm bunu kendim test edemiyorum çünkü telefondayım
Glogo

Aynı problemim var ve kimse bunu fark etmemiş gibi görünüyor, bu soruna herhangi bir çözüm buldunuz mu? @confile?
Reza.Ab

Bu sadece ihtiyacım olan şey ama işe yaramadı. Ben yapım çalıştı @interfaceve @implementationher ikisi (RenderViewToImage)ve ekleme #import "UIView+RenderViewToImage.h"de başlığına ViewController.hbu doğru kullanım yani? örneğin UIImageView *test = [[UIImageView alloc] initWithImage:[self imageByRenderingView]]; [self addSubview:test];?
Greg

ve bu yöntem, ViewController.moluşturmaya çalıştığım görünümdü- (UIView *)testView { UIView *v1 = [[UIView alloc] initWithFrame:CGRectMake(50, 50, 50, 50)]; v1.backgroundColor = [UIColor redColor]; UIView *v2 = [[UIView alloc] initWithFrame:CGRectMake(50, 50, 150, 150)]; v2.backgroundColor = [UIColor blueColor]; [v2 addSubview:v1]; return v2; }
Greg

15

Hızlı 3

Swift 3 (dayalı çözüm Dima cevabı UIView uzantılı) böyle olmalı:

extension UIView {
    public func getSnapshotImage() -> UIImage {
        UIGraphicsBeginImageContextWithOptions(self.bounds.size, self.isOpaque, 0)
        self.drawHierarchy(in: self.bounds, afterScreenUpdates: false)
        let snapshotImage: UIImage = UIGraphicsGetImageFromCurrentImageContext()!
        UIGraphicsEndImageContext()
        return snapshotImage
    }
}

6

Yeni iOS 10.0 API'sını ve önceki yöntemi destekleyen Drop-in Swift 3.0 uzantısı.

Not:

  • iOS sürüm kontrolü
  • Bağlam temizlemeyi basitleştirmek için erteleme kullanımına dikkat edin.
  • Ayrıca görünümün opaklığını ve geçerli ölçeğini uygular.
  • Hiçbir şey sadece !bir kilitlenmeye neden olabilir kullanarak açılmamış .

extension UIView
{
    public func renderToImage(afterScreenUpdates: Bool = false) -> UIImage?
    {
        if #available(iOS 10.0, *)
        {
            let rendererFormat = UIGraphicsImageRendererFormat.default()
            rendererFormat.scale = self.layer.contentsScale
            rendererFormat.opaque = self.isOpaque
            let renderer = UIGraphicsImageRenderer(size: self.bounds.size, format: rendererFormat)

            return
                renderer.image
                {
                    _ in

                    self.drawHierarchy(in: self.bounds, afterScreenUpdates: afterScreenUpdates)
                }
        }
        else
        {
            UIGraphicsBeginImageContextWithOptions(self.bounds.size, self.isOpaque, self.layer.contentsScale)
            defer
            {
                UIGraphicsEndImageContext()
            }

            self.drawHierarchy(in: self.bounds, afterScreenUpdates: afterScreenUpdates)

            return UIGraphicsGetImageFromCurrentImageContext()
        }
    }
}

5

Swift 2.0:

Uzantı yöntemini kullanarak:

extension UIImage{

   class func renderUIViewToImage(viewToBeRendered:UIView?) -> UIImage
   {
       UIGraphicsBeginImageContextWithOptions((viewToBeRendered?.bounds.size)!, false, 0.0)
       viewToBeRendered!.drawViewHierarchyInRect(viewToBeRendered!.bounds, afterScreenUpdates: true)
       viewToBeRendered!.layer.renderInContext(UIGraphicsGetCurrentContext()!)

       let finalImage = UIGraphicsGetImageFromCurrentImageContext()
       UIGraphicsEndImageContext()

       return finalImage
   }

}

Kullanımı:

override func viewDidLoad() {
    super.viewDidLoad()

    //Sample View To Self.view
    let sampleView = UIView(frame: CGRectMake(100,100,200,200))
    sampleView.backgroundColor =  UIColor(patternImage: UIImage(named: "ic_120x120")!)
    self.view.addSubview(sampleView)    

    //ImageView With Image
    let sampleImageView = UIImageView(frame: CGRectMake(100,400,200,200))

    //sampleView is rendered to sampleImage
    var sampleImage = UIImage.renderUIViewToImage(sampleView)

    sampleImageView.image = sampleImage
    self.view.addSubview(sampleImageView)

 }

3

Swift 3.0 uygulaması

extension UIView {
    func getSnapshotImage() -> UIImage {
        UIGraphicsBeginImageContextWithOptions(bounds.size, isOpaque, 0)
        drawHierarchy(in: bounds, afterScreenUpdates: false)
        let snapshotImage = UIGraphicsGetImageFromCurrentImageContext()!
        UIGraphicsEndImageContext()
        return snapshotImage
    }
}

3

Tüm Swift 3 cevapları benim için çalışmadı, bu yüzden en kabul edilen cevabı tercüme ettim:

extension UIImage {
    class func imageWithView(view: UIView) -> UIImage {
        UIGraphicsBeginImageContextWithOptions(view.bounds.size, view.isOpaque, 0.0)
        view.layer.render(in: UIGraphicsGetCurrentContext()!)
        let img: UIImage? = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        return img!
    }
}

2

İşte @Dima'nın cevabına dayanan bir Swift 4 UIView uzantısı .

extension UIView {
   func snapshotImage() -> UIImage? {
       UIGraphicsBeginImageContextWithOptions(bounds.size, isOpaque, 0)
       drawHierarchy(in: bounds, afterScreenUpdates: false)
       let image = UIGraphicsGetImageFromCurrentImageContext()
       UIGraphicsEndImageContext()
       return image
   }
}

2

Swift 5.1 için bu uzantıyı kullanabilirsiniz:

extension UIView {

    func asImage() -> UIImage {
        let renderer = UIGraphicsImageRenderer(bounds: bounds)

        return renderer.image {
            rendererContext in

            layer.render(in: rendererContext.cgContext)
        }
    }
}

1

UIGraphicsImageRendereriOS 10'da tanıtılan nispeten yeni bir API'dir . Bir nokta boyutu belirterek bir UIGraphicsImageRenderer oluşturursunuz . İmage yöntemi bir kapatma bağımsız değişkeni alır ve iletilen kapatmanın yürütülmesinden kaynaklanan bir bitmap döndürür. Bu durumda sonuç, belirtilen sınırlar içinde çizmek için küçültülmüş orijinal görüntüdür.

https://nshipster.com/image-resizing/

Bu nedenle, geçtiğiniz boyutun UIGraphicsImageRendererpiksel değil nokta olduğundan emin olun .

Görüntüleriniz beklediğinizden daha büyükse, boyutunuzu ölçek faktörüne bölmeniz gerekir.


Merak ediyorum, lütfen bu konuda bana yardım eder misin? stackoverflow.com/questions/61247577/… UIGraphicsImageRenderer kullanıyorum, sorun, dışa aktarılan görüntünün cihazdaki gerçek görünümden daha büyük olmasını istiyorum. Ancak istenen boyutta alt görünümler (etiketler) yeterince keskin değil - bu nedenle kalite kaybı var.
Ondřej Korol


0
- (UIImage*)screenshotForView:(UIView *)view
{
    UIGraphicsBeginImageContext(view.bounds.size);
    [view.layer renderInContext:UIGraphicsGetCurrentContext()];
    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();

    // hack, helps w/ our colors when blurring
    NSData *imageData = UIImageJPEGRepresentation(image, 1); // convert to jpeg
    image = [UIImage imageWithData:imageData];

    return image;
}

-2

Bu yöntemde bir görünüm nesnesini iletmeniz yeterlidir ve bir UIImage nesnesi döndürür.

-(UIImage*)getUIImageFromView:(UIView*)yourView
{
 UIGraphicsBeginImageContext(yourView.bounds.size);
 [yourView.layer renderInContext:UIGraphicsGetCurrentContext()];
 UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
 UIGraphicsEndImageContext();
 return image;
}

-6

Bu yöntemi UIView Kategorisine ekleyin

- (UIImage*) capture {
    UIGraphicsBeginImageContext(self.bounds.size);
    CGContextRef context = UIGraphicsGetCurrentContext();
    [self.layer renderInContext:context];
    UIImage *img = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return img;
}

2
Hiya orada, bu soruya iyi cevap verebilirken, lütfen diğer kullanıcıların sizin kadar bilgili olmayabileceğini unutmayın. Neden bu kodun işe yaradığına dair küçük bir açıklama eklemiyorsunuz? Teşekkürler!
Vogel612
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.