将一个3d变换应用到UIImageView.layer之后,我需要将生成的“视图”保存为一个新的UIImage ......起初看起来像一个简单的任务:-)但到目前为止没有运气,并且搜索没有'提出任何线索:-(所以我希望有人能够指出我正确的方向。
可以使用一个非常简单的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);
}
答案 0 :(得分:8)
我最终使用视图转换的反转在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
答案 1 :(得分:2)
理论上,你可以在黑色背景上快速渲染到屏幕后使用(现在允许的)未记录的调用UIGetScreenImage(),但实际上这将是缓慢而丑陋的,所以不要使用它; P。
答案 2 :(得分:2)
我和你有同样的问题,我找到了解决方案! 我想旋转UIImageView,因为我会有动画。 并保存图像,我使用这种方法:
void CGContextConcatCTM(CGContextRef c, CGAffineTransform transform)
变换参数是UIImageView的变换!因此,您对imageView所做的任何事情都与图像相同! 我已经写了一个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;
}
希望这会对你有所帮助。
答案 3 :(得分:1)
你有看过这个吗? UIImage from UIView
答案 4 :(得分:1)
我有同样的问题,我能够使用UIView的drawViewHierarchyInRect:afterScreenUpdates:方法,来自iOS 7.0 - (Documentation)
它绘制了屏幕上显示的整个树。
UIGraphicsBeginImageContextWithOptions(viewToRender.bounds.size, YES, 0);
[viewToRender drawViewHierarchyInRect:viewToRender.bounds afterScreenUpdates:YES];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
答案 5 :(得分:0)
在captureView:
方法中,尝试替换此行:
[view.layer renderInContext:UIGraphicsGetCurrentContext()];
用这个:
[view.layer.superlayer renderInContext:UIGraphicsGetCurrentContext()];
您可能需要调整用于创建图像上下文的大小。
我在API文档中没有看到任何说明renderInContext的内容:忽略3D转换。但是,转换适用于图层,而不是其内容,这就是为什么需要渲染超层以查看应用的转换。
请注意,在超级视图上调用drawRect:
肯定无效,因为drawRect:
不会绘制子视图。
答案 6 :(得分:0)
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
然后您将完全控制四边形中的每个点。 :)
答案 7 :(得分:0)
假设您有一个名为 imageView 的 UIImageView。 如果您应用 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()
答案 8 :(得分:-1)
我发现至少在我的案例中工作的解决方案是继承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_rotated.png
,而是使用arrow.png
图像并对其进行镜像并在上下文中绘制。