Jak przechwycić UIView do UIImage bez utraty jakości na wyświetlaczu siatkówki

303

Mój kod działa dobrze na normalnych urządzeniach, ale tworzy rozmyte obrazy na urządzeniach siatkówki.

Czy ktoś zna rozwiązanie mojego problemu?

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

    UIImage * img = UIGraphicsGetImageFromCurrentImageContext();

    UIGraphicsEndImageContext();

    return img;
}
Daniel
źródło
Rozmazany. Wydaje mi się, że zgubiła się odpowiednia skala ...
Daniel
Ja też. spotkałem ten sam problem.
RainCast,
Gdzie wyświetlasz obraz wynikowy? Jak wyjaśnili inni, uważam, że renderujesz widok na obraz ze skalą 1, podczas gdy twoje urządzenie ma skalę 2 lub 3. Zmniejszasz więc rozdzielczość do niższej rozdzielczości. Jest to utrata jakości, ale nie powinna się rozmazać. Ale kiedy ponownie spojrzysz na ten obraz (na tym samym ekranie?) Na urządzeniu ze skalą 2, obraz zostanie przekonwertowany z niskiej rozdzielczości na wyższą, przy użyciu 2 pikseli na piksel na zrzucie ekranu. To skalowanie w górę wykorzystuje interpolację najprawdopodobniej (domyślnie w iOS), uśredniając wartości kolorów dla dodatkowych pikseli.
Hans Terje Bakke

Odpowiedzi:

667

Przełącz się z używania UIGraphicsBeginImageContextna UIGraphicsBeginImageContextWithOptions(zgodnie z dokumentacją na tej stronie ). Podaj 0.0 dla skali (trzeci argument), a otrzymasz kontekst o współczynniku skali równym współczynnikowi ekranu.

UIGraphicsBeginImageContextużywa stałego współczynnika skali wynoszącego 1,0, więc w rzeczywistości otrzymujesz dokładnie taki sam obraz na iPhonie 4 jak na innych iPhone'ach. Założę się, że iPhone 4 stosuje filtr, gdy domyślnie go skalujesz, lub po prostu twój mózg odbiera go jako mniej ostry niż wszystko wokół.

Zgaduję:

#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;
}

A w Swift 4:

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
}
Tommy
źródło
6
Odpowiedź Tommy'ego jest w porządku, ale nadal musisz zaimportować, #import <QuartzCore/QuartzCore.h>aby usunąć renderInContext:ostrzeżenie.
gwdp
2
@WayneLiu można jawnie określić współczynnik skali 2,0, ale prawdopodobnie nie uzyskałby dokładnie obrazu o jakości iPhone 4, ponieważ wszelkie załadowane mapy bitowe byłyby wersjami standardowymi, a nie wersjami @ 2x. Nie sądzę, że istnieje na to rozwiązanie, ponieważ nie ma sposobu, aby pouczyć wszystkich, którzy obecnie wstrzymują się UIImagedo ponownego załadowania, ale wymuszają wersję w wysokiej rozdzielczości (i prawdopodobnie natrafilibyście na problemy z powodu mniejszej pamięci RAM dostępnej w wersji wstępnej - urządzenia i tak).
Tommy
6
Zamiast używać 0.0fparametru skali, czy jest bardziej akceptowalny w użyciu [[UIScreen mainScreen] scale], działa również.
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.Jest wyraźnie udokumentowany, więc moim zdaniem 0.0f jest prostsze i lepsze.
cprcrack
5
Ta odpowiedź jest nieaktualna w systemie iOS 7. Zobacz moją odpowiedź na temat nowej „najlepszej” metody, aby to zrobić.
Dima,
224

Akceptowana obecnie odpowiedź jest nieaktualna, przynajmniej jeśli wspierasz system iOS 7.

Oto, czego powinieneś używać, jeśli obsługujesz tylko iOS7 +:

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

Swift 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()
}

Zgodnie z tym artykułem widać, że nowa metoda iOS7 drawViewHierarchyInRect:afterScreenUpdates:jest wielokrotnie szybsza niż renderInContext:.reper

Dima
źródło
5
Nie ma wątpliwości, że drawViewHierarchyInRect:afterScreenUpdates:jest to o wiele szybsze. Właśnie uruchomiłem profiler czasu w Instrumentach. Moje generowanie obrazu wzrosło z 138 ms do 27 ms.
ThomasCle
9
Z jakiegoś powodu nie działało to dla mnie, gdy próbowałem utworzyć niestandardowe ikony dla znaczników Map Google; wszystko, co dostałem, to czarne prostokąty :(
JakeP,
6
@CarlosP próbowałeś ustawienie afterScreenUpdates:do YES?
Dima,
7
ustawiono po opcji ScreenSpdates na TAK, naprawiono dla mnie problem z czarnym prostokątem
hyouuu
4
Otrzymywałem także czarne obrazy. Okazało się, że jeśli wywołam kod viewDidLoadlub viewWillAppear:obrazy będą czarne; Musiałem to zrobić viewDidAppear:. Więc w końcu wróciłem do renderInContext:.
manicaesar
32

Stworzyłem rozszerzenie Swift oparte na rozwiązaniu @Dima:

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
    }
}

EDYCJA: Swift 4 ulepszona wersja

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()
    }
}

Stosowanie:

let view = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))  
let image = UIImage.imageWithView(view)
Heberti Almeida
źródło
2
Dzięki za pomysł! Na marginesie, możesz również odroczyć {UIGraphicsEndImageContext ()} natychmiast po rozpoczęciu kontekstu i unikać konieczności wprowadzania zmiennej lokalnej img;)
Dennis L
Dziękujemy bardzo za szczegółowe wyjaśnienie i sposób użycia!
Zeus
Dlaczego ta classfunkcja ma widok?
Rudolf Adamkovič
Działa to jak staticfunkcja, ale ponieważ nie ma potrzeby zastępowania, możesz po prostu zmienić ją na staticfunc zamiast class.
Heberti Almeida
25

iOS Szybki

Korzystanie z nowoczesnego UIGraphicsImageRenderer

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
    }
}
Alexander Belyavskiy
źródło
@ masaldana2 może jeszcze nie, ale jak tylko zaczną deprecjację rzeczy lub dodanie przyspieszane sprzętowo renderingu lub ulepszenia lepiej być na ramionach gigantów (AKA użyciu najnowszych API Apple)
Juan Boero
Czy ktoś zdaje sobie sprawę z tego, co porównuje to rendererdo starego drawHierarchy…?
Vive
24

Aby poprawić odpowiedzi @Tommy i @Dima, użyj poniższej kategorii, aby renderować UIView w UIImage z przezroczystym tłem i bez utraty jakości. Praca na iOS7. (Lub po prostu użyj tej metody w implementacji, zastępując selfodniesienie swoim obrazem)

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
Glogo
źródło
1
Jeśli użyję metody drawViewHierarchyInRect z funkcją afterScreenUpdates: TAK moje proporcje obrazu uległy zmianie i obraz jest zniekształcony.
confile
Czy dzieje się tak po zmianie zawartości uiview? Jeśli tak, spróbuj ponownie wygenerować ten obraz UIImage. Przykro mi, ale nie mogę tego przetestować, ponieważ jestem na telefonie
Glogo,
Mam ten sam problem i wydaje się, że nikt tego nie zauważył. Czy znalazłeś jakieś rozwiązanie tego problemu? @confile?
Reza.Ab
Właśnie tego potrzebuję, ale to nie zadziałało. Próbowałem co @interfacei @implementationjak (RenderViewToImage)i dodanie #import "UIView+RenderViewToImage.h"do nagłówka w ViewController.hwięc jest to poprawne wykorzystanie? na przykład UIImageView *test = [[UIImageView alloc] initWithImage:[self imageByRenderingView]]; [self addSubview:test];?
Greg
a tą metodą ViewController.mbył widok, który próbowałem renderować- (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

Szybki 3

Rozwiązanie Swift 3 (oparte na odpowiedzi Dimy ) z rozszerzeniem UIView powinno wyglądać tak:

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
    }
}
Mehdi Hosseinzadeh
źródło
6

Drop-in Swift 3.0 rozszerzenie, które obsługuje nowy interfejs API iOS 10.0 i poprzednią metodę.

Uwaga:

  • Sprawdzanie wersji iOS
  • Zwróć uwagę na użycie opcji odroczenia w celu uproszczenia czyszczenia kontekstu.
  • Zastosuje również nieprzezroczystość i aktualną skalę widoku.
  • Nic nie jest po prostu rozpakowane, !co mogłoby spowodować awarię.

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()
        }
    }
}
Leslie Godwin
źródło
5

Swift 2.0:

Za pomocą metody rozszerzenia:

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
   }

}

Stosowanie:

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)

 }
AG
źródło
3

Implementacja Swift 3.0

extension UIView {
    func getSnapshotImage() -> UIImage {
        UIGraphicsBeginImageContextWithOptions(bounds.size, isOpaque, 0)
        drawHierarchy(in: bounds, afterScreenUpdates: false)
        let snapshotImage = UIGraphicsGetImageFromCurrentImageContext()!
        UIGraphicsEndImageContext()
        return snapshotImage
    }
}
imike
źródło
3

Wszystkie odpowiedzi Swift 3 nie działały dla mnie, dlatego przetłumaczyłem najbardziej akceptowaną odpowiedź:

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!
    }
}
Marco Antonio Uzcategui Pescoz
źródło
2

Oto rozszerzenie Swift 4 UIView oparte na odpowiedzi z @Dima.

extension UIView {
   func snapshotImage() -> UIImage? {
       UIGraphicsBeginImageContextWithOptions(bounds.size, isOpaque, 0)
       drawHierarchy(in: bounds, afterScreenUpdates: false)
       let image = UIGraphicsGetImageFromCurrentImageContext()
       UIGraphicsEndImageContext()
       return image
   }
}
Danfordham
źródło
2

W przypadku Swift 5.1 możesz użyć tego rozszerzenia:

extension UIView {

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

        return renderer.image {
            rendererContext in

            layer.render(in: rendererContext.cgContext)
        }
    }
}
Andrey M.
źródło
1

UIGraphicsImageRendererjest stosunkowo nowym interfejsem API, wprowadzonym w iOS 10. Konstruujesz UIGraphicsImageRenderer poprzez określenie rozmiaru punktu . Metoda image przyjmuje argument zamknięcia i zwraca bitmapę wynikającą z wykonania przekazanego zamknięcia. W takim przypadku wynikiem jest oryginalny obraz przeskalowany w dół, aby narysować w określonych granicach.

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

Upewnij się więc, że rozmiar, na który przechodzisz, UIGraphicsImageRendererto punkty, a nie piksele.

Jeśli twoje zdjęcia są większe niż się spodziewasz, musisz podzielić swój rozmiar przez współczynnik skali.

pkamb
źródło
Jestem ciekawy, czy mógłbyś mi w tym pomóc? stackoverflow.com/questions/61247577/ ... Używam UIGraphicsImageRenderer, problem polega na tym, że chcę, aby eksportowany obraz był większy niż rzeczywisty widok na urządzeniu. Jednak przy pożądanych rozmiarach widoki podrzędne (etykiety) nie są wystarczająco ostre - więc następuje utrata jakości.
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;
}
PJRadadiya
źródło
-2

W tej metodzie wystarczy przekazać obiekt widoku, a zwróci on obiekt UIImage.

-(UIImage*)getUIImageFromView:(UIView*)yourView
{
 UIGraphicsBeginImageContext(yourView.bounds.size);
 [yourView.layer renderInContext:UIGraphicsGetCurrentContext()];
 UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
 UIGraphicsEndImageContext();
 return image;
}
Atanu Mondal
źródło
-6

Dodaj tę metodę do kategorii UIView

- (UIImage*) capture {
    UIGraphicsBeginImageContext(self.bounds.size);
    CGContextRef context = UIGraphicsGetCurrentContext();
    [self.layer renderInContext:context];
    UIImage *img = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return img;
}
Shafraz Buhary
źródło
2
Cześć, chociaż może to odpowiedzieć na pytanie, pamiętaj, że inni użytkownicy mogą nie być tak kompetentni jak ty. Dlaczego nie dodasz małego wyjaśnienia, dlaczego ten kod działa? Dzięki!
Vogel612,