【问题标题】:How do I create/render a UIImage from a 3D transformed UIImageView?如何从 3D 转换的 UIImageView 创建/渲染 UIImage?
【发布时间】:2009-12-22 20:39:34
【问题描述】:

对 UIImageView.layer 应用 3d 变换后,我需要将生成的“视图”保存为新的 UIImage...起初看起来像一个简单的任务 :-) 但到目前为止还没有运气,而且搜索还没有t 找到任何线索 :-( 所以我希望有人能善意地指出我正确的方向。

一个非常简单的 iPhone 项目可用here

谢谢。

- (void)transformImage {
    float degrees = 12.0;
    float zDistance = 250;
    CATransform3D transform3D = CATransform3DIdentity;
    transform3D.m34 = 1.0 / zDistance; // the m34 cell of the matrix controls perspective, and zDistance affects the "sharpness" of the transform
    transform3D = CATransform3DRotate(transform3D, DEGREES_TO_RADIANS(degrees), 1, 0, 0); // perspective transform on y-axis
    imageView.layer.transform = transform3D;
}

/* FAIL : capturing layer contents doesn't get the transformed image -- just the original

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

UIImage *image = [UIImage imageWithCGImage:newImageRef];

*/


/* FAIL : docs for renderInContext states that it does not render 3D transforms

UIGraphicsBeginImageContext(imageView.image.size);

[imageView.layer renderInContext:UIGraphicsGetCurrentContext()];

UIImage *image = UIGraphicsGetImageFromCurrentImageContext();

UIGraphicsEndImageContext();

*/
//
// header
//
#import <QuartzCore/QuartzCore.h>
#define DEGREES_TO_RADIANS(x) x * M_PI / 180
UIImageView *imageView;
@property (nonatomic, retain) IBOutlet UIImageView *imageView;

//
// code
//
@synthesize imageView;

- (void)transformImage {
    float degrees = 12.0;
    float zDistance = 250;
    CATransform3D transform3D = CATransform3DIdentity;
    transform3D.m34 = 1.0 / zDistance; // the m34 cell of the matrix controls perspective, and zDistance affects the "sharpness" of the transform
    transform3D = CATransform3DRotate(transform3D, DEGREES_TO_RADIANS(degrees), 1, 0, 0); // perspective transform on y-axis
    imageView.layer.transform = transform3D;
}

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

- (void)imageSavedToPhotosAlbum:(UIImage *)image didFinishSavingWithError:(NSError *)error contextInfo:(void *)contextInfo {
    NSString *title = @"Save to Photo Album";
    NSString *message = (error ? [error description] : @"Success!");
    UIAlertView *alert = [[UIAlertView alloc] initWithTitle:title message:message delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil];
    [alert show];
    [alert release];
}

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

【问题讨论】:

  • 您是否尝试过抓取包含转换后的视图的视图?
  • 不太清楚你的意思...我试过 [view image](其中 view 是我的 UIImageView),但返回的图像不是转换后的图像。
  • 找到答案?
  • 这个问题你得到答案了吗?请帮我。我有同样的问题,更改 3D 变换并保存图像后。有可能吗?
  • 那是因为图层内容没有被变换,[imageView.layer renderInContext:...]imageView的坐标系中渲染。转换应用在视图和它的父视图之间,因此为了有任何工作的机会,您需要将它粘贴在容器视图中并渲染容器(但如果文档说它没有) t 处理 3D 变换,那么它可能无论如何都不起作用,尽管我希望应用 某种 类型的变换)。

标签: iphone cocoa-touch core-animation


【解决方案1】:

我最终使用视图变换的逆向在 CPU 上为每个像素创建了一个渲染方法。

基本上,它将原始的 UIImageView 渲染为 UIImage。然后将UIImage中的每个像素乘以逆变换矩阵,生成变换后的UIImage。

RenderUIImageView.h

#import <UIKit/UIKit.h>
#import <QuartzCore/CATransform3D.h>
#import <QuartzCore/CALayer.h>

@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

【讨论】:

【解决方案2】:

理论上,您可以在将其快速渲染到黑色背景上的屏幕后使用(现在允许的)未记录调用 UIGetScreenImage(),但实际上这会很慢而且很难看,所以不要使用它;P。

【讨论】:

  • 现在允许 UIGetScreenImage steveperks.co.uk/post/…
  • 尝试调用 UIGetScreenImage 进行测试,但收到“警告:函数 'UIGetScreenImage' 的隐式声明”。我需要包含哪个标题?
  • 它是私有 API,所以它可能不在任何标题中。只需将您自己的声明 (extern CGImageRef UIGetScreenImage();) 放在调用它的文件中。如果函数实际上不存在,链接器会抱怨。
【解决方案3】:

我和你有同样的问题,我找到了解决办法! 我想旋转 UIImageView,因为我会有动画。 并保存图片,我用的是这个方法:

void CGContextConcatCTM(CGContextRef c, CGAffineTransform transform)

transform 参数是 UIImageView 的转换!因此,您对 imageView 所做的任何事情都与 image! 相同! 我写了一个UIImage的分类方法。

-(UIImage *)imageRotateByTransform:(CGAffineTransform)transform{
// calculate the size of the rotated view's containing box for our drawing space
UIView *rotatedViewBox = [[UIView alloc] initWithFrame:CGRectMake(0,0,self.size.width, self.size.height)];
rotatedViewBox.transform = transform;
CGSize rotatedSize = rotatedViewBox.frame.size;
[rotatedViewBox release];

// Create the bitmap context
UIGraphicsBeginImageContext(rotatedSize);
CGContextRef bitmap = UIGraphicsGetCurrentContext();

// Move the origin to the middle of the image so we will rotate and scale around the center.
CGContextTranslateCTM(bitmap, rotatedSize.width/2, rotatedSize.height/2);

//Rotate the image context using tranform
CGContextConcatCTM(bitmap, transform);
// Now, draw the rotated/scaled image into the context
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;

}

希望这会对你有所帮助。

【讨论】:

  • 这仅适用于二维变换。 CATransform3D 的应用程序不是您可以使用这样的代码捕获的。
  • @BradLarson 在渲染 CAtransform3d 方面有什么进展吗?我已经用我自己的错误代码在大上下文中解决了这个问题,但问题是我在 transform3d 图像中出现了黑色区域。
【解决方案4】:

你看过这个吗? UIImage from UIView

【讨论】:

    【解决方案5】:

    我有同样的问题,我可以使用 UIView 的 drawViewHierarchyInRect:afterScreenUpdates: 方法,从 iOS 7.0 - (Documentation)

    它绘制屏幕上出现的整棵树。

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

    【讨论】:

    • 它根本不起作用 - 不幸的是它在没有变换的情况下绘制它
    • 等一下,它确实适用于 3F 变换 - 但您必须上一层超级视图。完全生病了。
    【解决方案6】:

    在您的 captureView: 方法中,尝试替换此行:

    [view.layer renderInContext:UIGraphicsGetCurrentContext()];
    

    用这个:

    [view.layer.superlayer renderInContext:UIGraphicsGetCurrentContext()];
    

    您可能需要调整用于创建图像上下文的大小。

    我在 API 文档中没有看到任何说 renderInContext: ignores 3D transformations 的内容。但是,变换适用于图层,而不是其内容,这就是为什么您需要渲染超级图层才能看到应用的变换。

    请注意,在 superview 上调用 drawRect: 肯定不起作用,因为 drawRect: 不会绘制子视图。

    【讨论】:

    • 在 superlayer 上调用 renderInContext 给了我一些看起来更像屏幕截图的东西,但仍然没有渲染 3d 变换。 CALayer 文档说:重要提示:此方法的 Mac OS X v10.5 实现不支持整个 Core Animation 合成模型。 QCCompositionLayer、CAOpenGLLayer 和 QTMovieLayer 图层不会被渲染。此外,不会渲染使用 3D 变换的图层...
    • 无赖,伙计。我认为您需要使用 UIGetScreenImage() 或编写自己的低级事物来将像素从一个缓冲区转换到另一个缓冲区。
    【解决方案7】:

    UIImage / CGImageRef 上的 3D 变换

    我改进了 Marcos Fuentes 的答案。您应该能够自己计算每个像素的映射。虽然不完美,但它可以解决问题...

    它在这个存储库中可用http://github.com/hfossli/AGGeometryKit/

    有趣的文件是

    https://github.com/hfossli/AGGeometryKit/blob/master/Source/AGTransformPixelMapper.m

    https://github.com/hfossli/AGGeometryKit/blob/master/Source/CGImageRef%2BCATransform3D.m

    https://github.com/hfossli/AGGeometryKit/blob/master/Source/UIImage%2BCATransform3D.m


    UIView / UIImageView 上的 3D 变换

    https://stackoverflow.com/a/12820877/202451

    然后您将完全控制四边形中的每个点。 :)

    【讨论】:

    【解决方案8】:

    假设你有 UIImageView 称为 imageView。 如果您应用 3d 变换并尝试使用 UIGraphicsImageRenderer 渲染此视图,则会忽略变换。

    imageView.layer.transform = someTransform3d
    

    但是,如果您使用 CATransform3DGetAffineTransform 将 CATransform3d 转换为 CGAffine 变换并将其应用于图像视图的变换属性,则它可以工作。

     imageView.transform = CATransform3DGetAffineTransform(someTransform3d)
    

    然后,您可以使用下面的扩展将其保存为 UIImage

    extension UIView {
        func asImage() -> UIImage {
            let renderer = UIGraphicsImageRenderer(bounds: bounds)
            return renderer.image { rendererContext in
                layer.render(in: rendererContext.cgContext)
            }
        }
    }
    

    然后打电话

    let image = imageView.asImage()
    

    【讨论】:

      【解决方案9】:

      我发现至少在我的情况下有效的解决方案是子类化CALayer。当renderInContext: 消息发送到层时,该层会自动将该消息转发到其所有子层。所以我所要做的就是继承CALayer 并覆盖renderInContext: 方法并在提供的上下文中呈现我需要呈现的内容。

      例如,在我的代码中,我有一个图层,我将其内容设置为箭头图像:

      UIImage *image = [UIImage imageNamed:@"arrow.png"];
      MYLayer *myLayer = [[CALayer alloc] init];
      [myLayer setContents:(__bridge id)[image CGImage]];
      [self.mainLayer addSublayer:myLayer];
      

      现在,当我在箭头上的 Y 轴上应用 3D 180 度旋转并尝试执行 [self.mainLayer renderInContext:context] 之后,我仍然得到未旋转的图像。

      所以在我的子类MyLayer 中,我覆盖了renderInContext: 并使用已经旋转的图像在提供的上下文中进行绘制:

      - (void)renderInContext:(CGContextRef)ctx
      {
          NSLog(@"Rendered in context");
          UIImage *image = [UIImage imageNamed:@"arrow_rotated.png"];
          CGContextDrawImage(ctx, self.bounds, image.CGImage);
      }
      

      这在我的情况下有效,但是我可以看到,如果您进行大量 3D 变换,您可能无法为每种可能的情况准备好图像。在许多其他情况下,虽然应该可以在传递的上下文中使用 2D 变换来渲染 3D 变换的结果。例如,在我的情况下,我可以使用 arrow.png 图像而不是使用不同的图像 arrow_rotated.png 并将其镜像并在上下文中绘制它。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-07-03
        • 1970-01-01
        • 2016-01-08
        相关资源
        最近更新 更多