15 Stimmen

Wie erstelle/rendere ich ein UIImage aus einem 3D transformierten UIImageView?

Nachdem ich einen 3D-Transform auf eine UIImageView-Schicht angewendet habe, muss ich die resultierende "Ansicht" als neues UIImage speichern... Das schien zuerst eine einfache Aufgabe zu sein :-) aber bisher kein Glück, und die Suche hat keine Hinweise ergeben :-( also hoffe ich, dass mir jemand freundlicherweise den richtigen Weg weisen wird.

Ein sehr einfaches iPhone-Projekt ist hier verfügbar.

Danke.

- (void)transformImage {
    float grad = 12.0;
    float zAbstand = 250;
    CATransform3D transform3D = CATransform3DIdentity;
    transform3D.m34 = 1.0 / zAbstand; // die Zelle m34 der Matrix steuert die Perspektive, und zAbstand beeinflusst die "Schärfe" des Transformations
    transform3D = CATransform3DRotate(transform3D, DEGREES_TO_RADIANS(grad), 1, 0, 0); // Perspektiventransformation auf der y-Achse
    imageView.layer.transform = transform3D;
}

/* FEHLER: Das Erfassen des Schichteninhalts ergibt nicht das transformierte Bild -- nur das Original

CGImageRef newImageRef = (CGImageRef)imageView.layer.contents;

UIImage *bild = [UIImage imageWithCGImage:newImageRef];

*/

/* FEHLER: Die Dokumentation für renderInContext besagt, dass sie keine 3D-Transformationen rendert

UIGraphicsBeginImageContext(imageView.image.size);

[imageView.layer renderInContext:UIGraphicsGetCurrentContext()];

UIImage *bild = UIGraphicsGetImageFromCurrentImageContext();

UIGraphicsEndImageContext();

*/
//
// Überschrift
//
#import 
#define DEGREES_TO_RADIANS(x) x * M_PI / 180
UIImageView *imageView;
@property (nonatomic, retain) IBOutlet UIImageView *imageView;

//
// Code
//
@synthesize imageView;

- (void)transformImage {
    float grad = 12.0;
    float zAbstand = 250;
    CATransform3D transform3D = CATransform3DIdentity;
    transform3D.m34 = 1.0 / zAbstand; // die Zelle m34 der Matrix steuert die Perspektive, und zAbstand beeinflusst die "Schärfe" des Transformations
    transform3D = CATransform3DRotate(transform3D, DEGREES_TO_RADIANS(grad), 1, 0, 0); // Perspektiventransformation auf der y-Achse
    imageView.layer.transform = transform3D;
}

- (UIImage *)captureView:(UIImageView *)ansicht {
    UIGraphicsBeginImageContext(ansicht.frame.size);
    [ansicht.layer renderInContext:UIGraphicsGetCurrentContext()];
    UIImage *neuesBild = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return neuesBild;
}

- (void)imageSavedToPhotosAlbum:(UIImage *)bild didFinishSavingWithError:(NSError *)fehler contextInfo:(void *)kontextInfo {
    NSString *titel = @"In Fotoalbum speichern";
    NSString *nachricht = (fehler ? [fehler description] : @"Erfolg!");
    UIAlertView *alert = [[UIAlertView alloc] initWithTitle:titel message:nachricht delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil];
    [alert show];
    [alert release];
}

- (IBAction)saveButtonClicked:(id)sender {
    UIImage *neuesBild = [self captureView:imageView];
    UIImageWriteToSavedPhotosAlbum(neuesBild, self, @selector(imageSavedToPhotosAlbum: didFinishSavingWithError: contextInfo:), nil);  
}

0 Stimmen

Hast du versucht, die Ansicht zu greifen, die deine transformierte Ansicht enthält?

0 Stimmen

Nicht ganz sicher, was du meinst... Ich habe [view image] ausprobiert (wobei view mein UIImageView ist), aber das zurückgegebene Bild ist nicht das transformierte Bild.

1 Stimmen

Finde eine Antwort darauf?

8voto

Marcos Fuentes Punkte 105

Letztendlich habe ich eine Render-Methode pixelweise auf der CPU erstellt, die die Inverse der Ansichtstransformation verwendet.

Im Grunde rendert es das originale UIImageView in ein UIImage. Anschließend wird jedes Pixel im UIImage mit der inversen Transformationsmatrix multipliziert, um das transformierte UIImage zu generieren.

RenderUIImageView.h

#import 
#import 
#import 

@interface RenderUIImageView : UIImageView

- (UIImage *)generateImage;

@end

RenderUIImageView.m

#import "RenderUIImageView.h"

@interface RenderUIImageView()

@property (assign) CATransform3D transform;
@property (assign) CGRect rect;

@property (assign) float denominatorx;
@property (assign) float denominatory;
@property (assign) float denominatorw;

@property (assign) float factor;

@end

@implementation RenderUIImageView

- (UIImage *)generateImage
{

    _transform = self.layer.transform;

    _denominatorx = _transform.m12 * _transform.m21 - _transform.m11  * _transform.m22 + _transform.m14 * _transform.m22 * _transform.m41 - _transform.m12 * _transform.m24 * _transform.m41 - _transform.m14 * _transform.m21 * _transform.m42 +
    _transform.m11 * _transform.m24 * _transform.m42;

    _denominatory = -_transform.m12 *_transform.m21 + _transform.m11 *_transform.m22 - _transform.m14 *_transform.m22 *_transform.m41 + _transform.m12 *_transform.m24 *_transform.m41 + _transform.m14 *_transform.m21 *_transform.m42 -
    _transform.m11* _transform.m24 *_transform.m42;

    _denominatorw = _transform.m12 *_transform.m21 - _transform.m11 *_transform.m22 + _transform.m14 *_transform.m22 *_transform.m41 - _transform.m12 *_transform.m24 *_transform.m41 - _transform.m14 *_transform.m21 *_transform.m42 +
    _transform.m11 *_transform.m24 *_transform.m42;

    _rect = self.bounds;

    if (UIGraphicsBeginImageContextWithOptions != NULL) {

        UIGraphicsBeginImageContextWithOptions(_rect.size, NO, 0.0);
    } else {
        UIGraphicsBeginImageContext(_rect.size);
    }

    if ([[UIScreen mainScreen] respondsToSelector:@selector(displayLinkWithTarget:selector:)] &&
        ([UIScreen mainScreen].scale == 2.0)) {
        _factor = 2.0f;
    } else {
        _factor = 1.0f;
    }

    UIImageView *img = [[UIImageView alloc] initWithFrame:_rect];
    img.image = self.image;

    [img.layer renderInContext:UIGraphicsGetCurrentContext()];
    UIImage *source = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();

    CGContextRef ctx;
    CGImageRef imageRef = [source CGImage];
    NSUInteger width = CGImageGetWidth(imageRef);
    NSUInteger height = CGImageGetHeight(imageRef);
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();

    unsigned char *inputData = malloc(height * width * 4);
    unsigned char *outputData = malloc(height * width * 4);

    NSUInteger bytesPerPixel = 4;
    NSUInteger bytesPerRow = bytesPerPixel * width;
    NSUInteger bitsPerComponent = 8;

    CGContextRef context = CGBitmapContextCreate(inputData, width, height,
                                                 bitsPerComponent, bytesPerRow, colorSpace,
                                                 kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
    CGColorSpaceRelease(colorSpace);
    CGContextDrawImage(context, CGRectMake(0, 0, width, height), imageRef);
    CGContextRelease(context);

    context = CGBitmapContextCreate(outputData, width, height,
                                    bitsPerComponent, bytesPerRow, colorSpace,
                                    kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
    CGColorSpaceRelease(colorSpace);
    CGContextDrawImage(context, CGRectMake(0, 0, width, height), imageRef);
    CGContextRelease(context);

    for (int ii = 0 ; ii < width * height ; ++ii)
    {
        int x = ii % width;
        int y = ii / width;
        int indexOutput = 4 * x + 4 * width * y;

        CGPoint p = [self modelToScreen:(x*2/_factor - _rect.size.width)/2.0 :(y*2/_factor - _rect.size.height)/2.0];

        p.x *= _factor;
        p.y *= _factor;

        int indexInput = 4*(int)p.x + (4*width*(int)p.y);

        if (p.x >= width || p.x < 0 || p.y >= height || p.y < 0 || indexInput >  width * height *4)
        {
            outputData[indexOutput] = 0.0;
            outputData[indexOutput+1] = 0.0;
            outputData[indexOutput+2] = 0.0;
            outputData[indexOutput+3] = 0.0;

        }
        else
        {
            outputData[indexOutput] = inputData[indexInput];
            outputData[indexOutput+1] = inputData[indexInput + 1];
            outputData[indexOutput+2] = inputData[indexInput + 2];
            outputData[indexOutput+3] = 255.0;
        }
    }

    ctx = CGBitmapContextCreate(outputData,CGImageGetWidth( imageRef ),CGImageGetHeight( imageRef ),8,CGImageGetBytesPerRow( imageRef ),CGImageGetColorSpace( imageRef ), kCGImageAlphaPremultipliedLast );

    imageRef = CGBitmapContextCreateImage (ctx);

    UIImage* rawImage = [UIImage imageWithCGImage:imageRef];
    CGContextRelease(ctx);
    free(inputData);
    free(outputData);
    return rawImage;
}

- (CGPoint) modelToScreen : (float) x: (float) y
{
    float xp = (_transform.m22 *_transform.m41 - _transform.m21 *_transform.m42 - _transform.m22* x + _transform.m24 *_transform.m42 *x + _transform.m21* y - _transform.m24* _transform.m41* y) / _denominatorx;        
    float yp = (-_transform.m11 *_transform.m42 + _transform.m12 * (_transform.m41 - x) + _transform.m14 *_transform.m42 *x + _transform.m11 *y - _transform.m14 *_transform.m41* y) / _denominatory;        
    float wp = (_transform.m12 *_transform.m21 - _transform.m11 *_transform.m22 + _transform.m14*_transform.m22* x - _transform.m12 *_transform.m24* x - _transform.m14 *_transform.m21* y + _transform.m11 *_transform.m24 *y) / _denominatorw;

    CGPoint result = CGPointMake(xp/wp, yp/wp);
    return result;
}

@end

0 Stimmen

Hast du diesen Code selbst geschrieben? Welche Lizenz hat er? Da er auf Stackoverflow ist, nehme ich an er ist frei verfügbar?

0 Stimmen

Nur damit du es weißt, ich habe diesen Code überarbeitet und verbessert. Du kannst ihn hier überprüfen github.com/hfossli/AGGeometryKit/blob/master/Source/… und github.com/hfossli/AGGeometryKit/blob/master/Source/…

0 Stimmen

@MarcosFuentes, hfossli, Können Sie erklären, warum anstelle der Transformation selbst eine Rücktransformation verwendet wird?

2voto

Grant Paul Punkte 5754

Theoretisch könntest du den (jetzt erlaubten) undokumentierten Aufruf UIGetScreenImage() verwenden, nachdem du ihn schnell auf den Bildschirm gerendert hast. auf einem schwarzen Hintergrund, aber in der Praxis wird dies langsam und hässlich sein, also benutze es nicht ;P.

0 Stimmen

UIGetScreenImage ist jetzt erlaubt steveperks.co.uk/post/…

0 Stimmen

Versuchte, UIGetScreenImage aufzurufen, um dies zu testen, aber ich bekomme "Warnung: implizite Deklaration der Funktion 'UIGetScreenImage'". Welchen Header muss ich einbinden?

1 Stimmen

Es handelt sich um eine private API, daher steht sie wahrscheinlich nicht in irgendwelchen Überschriften. Setzen Sie einfach Ihre eigene Deklaration (extern CGImageRef UIGetScreenImage();) irgendwo in die Datei, die sie aufruft. Falls die Funktion tatsächlich nicht vorhanden ist, wird der Linker sich beschweren.

2voto

yuyi Punkte 31

Ich habe das gleiche Problem wie du und ich habe die Lösung gefunden! Ich möchte das UIImageView drehen, weil ich die Animation haben werde. Und um das Bild zu speichern, verwende ich diese Methode:

void CGContextConcatCTM(CGContextRef c, CGAffineTransform transform)

Der transform-Parameter ist die Transformation deines UIImageView!. Also alles, was du mit dem ImageView gemacht hast, wird mit dem Bild gleich sein!. Und ich habe eine Kategoriemethode von UIImage geschrieben.

-(UIImage *)imageRotateByTransform:(CGAffineTransform)transform{
// Berechne die Größe des um den Transformierten Bildrahmen für unseren Zeichnungsbereich
UIView *rotatedViewBox = [[UIView alloc] initWithFrame:CGRectMake(0,0,self.size.width, self.size.height)];
rotatedViewBox.transform = transform;
CGSize rotatedSize = rotatedViewBox.frame.size;
[rotatedViewBox release];

// Erstelle den Bitmap-Kontext
UIGraphicsBeginImageContext(rotatedSize);
CGContextRef bitmap = UIGraphicsGetCurrentContext();

// Bewege den Ursprung in die Mitte des Bildes, damit wir um das Zentrum drehen und skalieren können.
CGContextTranslateCTM(bitmap, rotatedSize.width/2, rotatedSize.height/2);

// Drehe den Bildkontext mithilfe von transform
CGContextConcatCTM(bitmap, transform);
// Jetzt das gedrehte/skalierte Bild in den Kontext zeichnen
CGContextScaleCTM(bitmap, 1.0, -1.0);
CGContextDrawImage(bitmap, CGRectMake(-self.size.width / 2, -self.size.height / 2, self.size.width, self.size.height), [self CGImage]);

UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return newImage;

}

Ich hoffe, das hilft dir.

7 Stimmen

Dies funktioniert nur mit 2-D-Transformationen. Die Anwendung eines CATransform3D ist nichts, was Sie mit diesem Code erfassen können.

0 Stimmen

@BradLarson Gibt es Fortschritte beim Rendern von CATransform3D? Ich habe es mit meinem eigenen fehlerhaften Code im großen Kontext herausgefunden, aber das Problem dabei ist, dass ich einen schwarzen Bereich mit dem Transform3D-Bild bekomme.

1voto

slf Punkte 22309

Hast du dir das schon angesehen? UIImage von UIView

1voto

Ellery Russell Punkte 11

Ich hatte das gleiche Problem, ich konnte die Methode drawViewHierarchyInRect:afterScreenUpdates: von UIView verwenden, ab iOS 7.0 - (Dokumentation)

Es zeichnet den gesamten Baum so, wie er auf dem Bildschirm erscheint.

UIGraphicsBeginImageContextWithOptions(viewToRender.bounds.size, YES, 0);
[viewToRender drawViewHierarchyInRect:viewToRender.bounds afterScreenUpdates:YES];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();

0 Stimmen

Es funktioniert überhaupt nicht - es zeichnet es leider ohne Transformationen

0 Stimmen

Warten Sie, es funktioniert mit 3F-Transformationen - ABER SIE MÜSSEN EINE EBENE HÖHERES ÜBERBLICK. total krass.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X