I'm following the instructions outlined in this answer to fill a layer with a pattern in Core Graphics. When the layer is a CALayer subclass, the drawing works fine. However, when the layer is a CATiledLayer subclass, I get an EXC_BAD_ACCESS error at runtime.
static void drawPatternImage (void *info, CGContextRef ctx)
{
CGImageRef image = (CGImageRef) info;
CGContextDrawImage(ctx,
CGRectMake(0,0, CGImageGetWidth(image),CGImageGetHeight(image)),
image); // EXC_BAD_ACCESS here :(
}
static void releasePatternImage( void *info )
{
CGImageRelease((CGImageRef)info);
}
// pattern creation
int width = CGImageGetWidth(image);
int height = CGImageGetHeight(image);
static const CGPatternCallbacks callbacks = {0, &drawPatternImage, &releasePatternImage};
CGPatternRef pattern = CGPatternCreate (image,
CGRectMake (0, 0, width, height),
CGAffineTransformMake (1, 0, 0, 1, 0, 0),
width,
height,
kCGPatternTilingConstantSpacing,
true,
&callbacks);
CGColorSpaceRef space = CGColorSpaceCreatePattern(NULL);
CGFloat components[1] = {1.0};
CGColorRef color = CGColorCreateWithPattern(space, pattern, components);
CGColorSpaceRelease(space);
CGPatternRelease(pattern);
theLayer.backgroundColor = color;
CGColorRelease(color);
What do I need to do to draw a patterned image in a CATiledLayer subclass?
i use "CGContextDrawTiledImage" in the "drawLayer" method for a CATiledLayer class:
in .h:
#interface StarView : UIView {
UIImage *imgTextureCarta;
}
in .m
- (id)initWithFrame:(CGRect)frame {
NSString* imagePath = [ [ NSBundle mainBundle] pathForResource:#"aTexture" ofType:#"png"];
imgTextureCarta = [UIImage imageWithContentsOfFile: imagePath];
[imgTextureCarta retain];
CGImageRef texureCarta;
texureCarta = (imgTextureCarta.CGImage);
}
-(void)drawLayer:(CALayer *)layer inContext:(CGContextRef)context{
CGImageRef texureCarta;
texureCarta = (imgTextureCarta.CGImage);
CGRect bounds = layer.bounds;
CGContextSetBlendMode (context, 0);
CGContextClipToRect(context, layer.bounds);
CGContextDrawTiledImage(context, CGRectMake(0,0, 128, 120), texureCarta);
}
- (void)dealloc {
[imgTextureCarta release];
[super dealloc];
}
Related
Currently I'm applying a corner radius and shadow to multiple UIView's in a scrollview. I've noticed that adding a corner radius and shadow makes the scrollview lag like crazy whenever I scroll. How can I apply these affects without having my performance suffer?
Try by setting also the shadowPath of the layer:
view.layer.cornerRadius=6.0f;
view.layer.borderWidth=2.0f;
view.layer.borderColor=[UIColor grayColor].CGColor;
view.layer.shadowColor = [UIColor blackColor].CGColor;
view.layer.shadowOpacity = 0.3f;
view.layer.shadowOffset = CGSizeMake(0, 0.0f);
view.layer.masksToBounds = NO;
UIBezierPath *path = [UIBezierPath bezierPathWithRect:view.bounds];
view.layer.shadowPath = path.CGPath;
+(UIImage *)makeRoundCornerImage : (UIImage*) img : (int) cornerWidth : (int) cornerHeight
{
UIImage * newImage = nil;
if( nil != img)
{
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
int w = img.size.width;
int h = img.size.height;
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef context = CGBitmapContextCreate(NULL, w, h, 8, 4 * w, colorSpace, kCGImageAlphaPremultipliedFirst);
CGContextBeginPath(context);
CGRect rect = CGRectMake(0, 0, img.size.width, img.size.height);
addRoundedRectToPath(context, rect, cornerWidth, cornerHeight);
CGContextClosePath(context);
CGContextClip(context);
CGContextDrawImage(context, CGRectMake(0, 0, w, h), img.CGImage);
CGImageRef imageMasked = CGBitmapContextCreateImage(context);
CGContextRelease(context);
CGColorSpaceRelease(colorSpace);
[img release];
newImage = [[UIImage imageWithCGImage:imageMasked] retain];
CGImageRelease(imageMasked);
[pool release];
}
return newImage;
}
// With the help of This method we can create the round corner of any PNG file
UIImage *imageFromFile = [UIImage imageNamed:#"FilterFields.png"];
imageFromFile = [ImageManipulator
makeRoundCornerImage:imageFromFile : 10 : 10];
[SmallTable setImage:imageFromFile];
I'm a beginner of iOS programing.
I want to display image on screen for frame by frame.
But in my source, only first frame is drawn on screen and ignored all of next frames.
How do I do to redisplay for realtime?
My image displaying Source :
- viewDidLoad : init display first screen
- ChangeImage : change display next screens
- setPixelColorR:G:B:X:Y: : change a pixel color on screen
<ContentViewController.h>
#import <UIKit/UIKit.h>
#interface ContentViewController : UIViewController {
IBOutlet UIImageView *svContent;
CGContextRef context;
size_t dataSize;
UInt8* initdata;
}
#property(nonatomic,retain) IBOutlet UIImageView *svContent;
-(IBAction)btnPhotosTouched;
- (void) ChangeImage;
- (void) setPixelColorR:(int)red G:(int)green B:(int)blue X:(int)x Y:(int)y;
#end
AND
<ContentViewController.m>
#import "ContentViewController.h"
#implementation ContentViewController
#synthesize svContent;
- (void)viewDidLoad {
[super viewDidLoad];
UIImageView *imageView;
CGSize screenSize = [[UIScreen mainScreen] bounds].size;
CGFloat width = screenSize.width;
CGFloat height = screenSize.height;
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
size_t bitsPerComponent = 8;
size_t bytesPerPixel = 4;
size_t bytesPerRow = (width * bitsPerComponent * bytesPerPixel + 7) / 8;
dataSize = bytesPerRow * height;
initdata = malloc(sizeof(UInt8) * dataSize);
memset(initdata, 0, dataSize);
context = CGBitmapContextCreate(initdata, width, height, bitsPerComponent, bytesPerRow, colorSpace, kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Big);
// a drawing for a test.
CGContextSetRGBFillColor (context, 1, 0, 0, 1);
CGContextFillRect (context, CGRectMake (0, 0, 200, 100 ));
CGContextSetRGBFillColor (context, 0, 0, 1, .5);
CGContextFillRect (context, CGRectMake (0, 0, 100, 200));
CGColorSpaceRelease(colorSpace);
CGImageRef imageRef = CGBitmapContextCreateImage(context);
UIImage *result = [[UIImage imageWithCGImage:imageRef] retain];
CGImageRelease(imageRef);
imageView = [[UIImageView alloc] initWithImage:result];
[svContent addSubview:imageView];
[imageView release];
// [self ChangeImage:initdata];
}
- (void) ChangeImage
{
UIImageView *imageView;
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGColorSpaceRelease(colorSpace);
CGImageRef imageRef = CGBitmapContextCreateImage(context);
UIImage *result = [[UIImage imageWithCGImage:imageRef] retain];
CGImageRelease(imageRef);
imageView = [[UIImageView alloc] initWithImage:result];
[svContent addSubview:imageView];
[imageView release];
NSLog(#"Thread Image Change GO");
[svContent setNeedsDisplay];
[self.view setNeedsDisplay];
[super.view setNeedsDisplay];
}
- (void) setPixelColorR:(int)red G:(int)green B:(int)blue X:(int)x Y:(int)y
{
CGFloat r = (CGFloat)red / 255.0f;
CGFloat g = (CGFloat)green / 255.0f;
CGFloat b = (CGFloat)blue / 255.0f;
CGContextSetRGBFillColor(context, r, g, b, 1);
CGContextFillRect(context, CGRectMake(x, y, 1, 1));
}
.
.
.
#end
Thank you.
I have a small augmented reality app that I'm developing and would like to know how to save a screenshot of what the user sees with a tap of a button or a timer.
The app works by overlaying live camera feed above another UIView. I can save screenshots by using power button +home button, these are saved to camera roll. However, Apple will not render the AVCaptureVideoPreviewLayer, even if I ask the window to save itself. It will create a transparent piece of canvas where the preview layer is.
What's the proper way for an augmented reality app to save screenshots, including transparency and subviews?
//displaying a live preview on one of the views
-(void)startCapture
{
captureSession = [[AVCaptureSession alloc] init];
AVCaptureDevice *audioCaptureDevice = nil;
// AVCaptureDevice *audioCaptureDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
NSArray *videoDevices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
for (AVCaptureDevice *device in videoDevices) {
if(useFrontCamera){
if (device.position == AVCaptureDevicePositionFront) {
//FRONT-FACING CAMERA EXISTS
audioCaptureDevice = device;
break;
}
}else{
if (device.position == AVCaptureDevicePositionBack) {
//Rear-FACING CAMERA EXISTS
audioCaptureDevice = device;
break;
}
}
}
NSError *error = nil;
AVCaptureDeviceInput *audioInput = [AVCaptureDeviceInput deviceInputWithDevice:audioCaptureDevice error:&error];
if (audioInput) {
[captureSession addInput:audioInput];
}
else {
// Handle the failure.
}
if([captureSession canAddOutput:captureOutput]){
captureOutput = [[AVCaptureVideoDataOutput alloc] init];
[captureOutput setAlwaysDiscardsLateVideoFrames:YES];
[captureOutput setSampleBufferDelegate:self queue:queue];
[captureOutput setVideoSettings:videoSettings];
dispatch_release(queue);
}else{
//handle failure
}
previewLayer = [AVCaptureVideoPreviewLayer layerWithSession:captureSession];
UIView *aView = arOverlayView;
previewLayer.frame =CGRectMake(0,0, arOverlayView.frame.size.width,arOverlayView.frame.size.height); // Assume you want the preview layer to fill the view.
[aView.layer addSublayer:previewLayer];
[captureSession startRunning];
}
//ask the entire window to draw itself in a graphics context. This call will not render
//the AVCaptureVideoPreviewLayer . It has to be replaced with a UIImageView or GL based view.
//see following code for creating a dynamically updating UIImageView
-(void)saveScreenshot
{
UIGraphicsBeginImageContext(appDelegate.window.bounds.size);
[appDelegate.window.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *screenshot = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
UIImageWriteToSavedPhotosAlbum(screenshot, self,
#selector(image:didFinishSavingWithError:contextInfo:), nil);
}
//image saved to camera roll callback
- (void)image:(UIImage *)image didFinishSavingWithError:(NSError *)error
contextInfo:(void *)contextInfo
{
// Was there an error?
if (error != NULL)
{
// Show error message...
NSLog(#"save failed");
}
else // No errors
{
NSLog(#"save successful");
// Show message image successfully saved
}
}
Here's the code for creating the image:
//you need to add your view controller as a delegate to the camera output to be notified of buffereed data
-(void)activateCameraFeed
{
//this is the code responsible for capturing feed for still image processing
dispatch_queue_t queue = dispatch_queue_create("com.AugmentedRealityGlamour.ImageCaptureQueue", NULL);
captureOutput = [[AVCaptureVideoDataOutput alloc] init];
[captureOutput setAlwaysDiscardsLateVideoFrames:YES];
[captureOutput setSampleBufferDelegate:self queue:queue];
[captureOutput setVideoSettings:videoSettings];
dispatch_release(queue);
//......configure audio feed, add inputs and outputs
}
//buffer delegate callback
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection
{
if ( ignoreImageStream )
return;
[self performImageCaptureFrom:sampleBuffer];
}
Create a UIImage:
- (void) performImageCaptureFrom:(CMSampleBufferRef)sampleBuffer
{
CVImageBufferRef imageBuffer;
if ( CMSampleBufferGetNumSamples(sampleBuffer) != 1 )
return;
if ( !CMSampleBufferIsValid(sampleBuffer) )
return;
if ( !CMSampleBufferDataIsReady(sampleBuffer) )
return;
imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
if ( CVPixelBufferGetPixelFormatType(imageBuffer) != kCVPixelFormatType_32BGRA )
return;
CVPixelBufferLockBaseAddress(imageBuffer,0);
uint8_t *baseAddress = (uint8_t *)CVPixelBufferGetBaseAddress(imageBuffer);
size_t bytesPerRow = CVPixelBufferGetBytesPerRow(imageBuffer);
size_t width = CVPixelBufferGetWidth(imageBuffer);
size_t height = CVPixelBufferGetHeight(imageBuffer);
CGImageRef newImage = nil;
if ( cameraDeviceSetting == CameraDeviceSetting640x480 )
{
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef newContext = CGBitmapContextCreate(baseAddress, width, height, 8, bytesPerRow, colorSpace, kCGBitmapByteOrder32Little | kCGImageAlphaPremultipliedFirst);
newImage = CGBitmapContextCreateImage(newContext);
CGColorSpaceRelease( colorSpace );
CGContextRelease(newContext);
}
else
{
uint8_t *tempAddress = malloc( 640 * 4 * 480 );
memcpy( tempAddress, baseAddress, bytesPerRow * height );
baseAddress = tempAddress;
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef newContext = CGBitmapContextCreate(baseAddress, width, height, 8, bytesPerRow, colorSpace, kCGBitmapByteOrder32Little | kCGImageAlphaNoneSkipFirst);
newImage = CGBitmapContextCreateImage(newContext);
CGContextRelease(newContext);
newContext = CGBitmapContextCreate(baseAddress, 640, 480, 8, 640*4, colorSpace, kCGBitmapByteOrder32Little | kCGImageAlphaPremultipliedFirst);
CGContextScaleCTM( newContext, (CGFloat)640/(CGFloat)width, (CGFloat)480/(CGFloat)height );
CGContextDrawImage(newContext, CGRectMake(0,0,640,480), newImage);
CGImageRelease(newImage);
newImage = CGBitmapContextCreateImage(newContext);
CGColorSpaceRelease( colorSpace );
CGContextRelease(newContext);
free( tempAddress );
}
if ( newImage != nil )
{
//modified for iOS5.0 with ARC
tempImage = [[UIImage alloc] initWithCGImage:newImage scale:(CGFloat)1.0 orientation:cameraImageOrientation];
CGImageRelease(newImage);
//this call creates the illusion of a preview layer, while we are actively switching images created with this method
[self performSelectorOnMainThread:#selector(newCameraImageNotification:) withObject:tempImage waitUntilDone:YES];
}
CVPixelBufferUnlockBaseAddress(imageBuffer,0);
}
update the interface with a UIView that can actually be rendered in a graphics context:
- (void) newCameraImageNotification:(UIImage*)newImage
{
if ( newImage == nil )
return;
[arOverlayView setImage:newImage];
//or do more advanced processing of the image
}
If you are wanting a snapshot of what's on screen, this is what I'm doing in one of my camera apps. I haven't touched this code in a long time so there might be a better 5.0 way now but this is solid with over 1 million downloads. There is a function for grabbing a UIView based screen and one for grabbing an Open/GLES1 screen:
//
// ScreenCapture.m
// LiveEffectsCam
//
// Created by John Carter on 10/8/10.
//
#import "ScreenCapture.h"
#import <QuartzCore/CABase.h>
#import <QuartzCore/CATransform3D.h>
#import <QuartzCore/CALayer.h>
#import <QuartzCore/CAScrollLayer.h>
#import <OpenGLES/EAGL.h>
#import <OpenGLES/ES1/gl.h>
#import <OpenGLES/ES1/glext.h>
#import <QuartzCore/QuartzCore.h>
#import <OpenGLES/EAGLDrawable.h>
#implementation ScreenCapture
+ (UIImage *) GLViewToImage:(GLView *)glView
{
UIImage *glImage = [GLView snapshot:glView]; // returns an autoreleased image
return glImage;
}
+ (UIImage *) GLViewToImage:(GLView *)glView withOverlayImage:(UIImage *)overlayImage
{
UIImage *glImage = [GLView snapshot:glView]; // returns an autoreleased image
// Merge Image and Overlay
//
CGRect imageRect = CGRectMake((CGFloat)0.0, (CGFloat)0.0, glImage.size.width*glImage.scale, glImage.size.height*glImage.scale);
CGImageRef overlayCopy = CGImageCreateCopy( overlayImage.CGImage );
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef context = CGBitmapContextCreate(NULL, (int)glImage.size.width*glImage.scale, (int)glImage.size.height*glImage.scale, 8, (int)glImage.size.width*4*glImage.scale, colorSpace, kCGImageAlphaPremultipliedLast);
CGContextDrawImage(context, imageRect, glImage.CGImage);
CGContextDrawImage(context, imageRect, overlayCopy);
CGImageRef newImage = CGBitmapContextCreateImage(context);
UIImage *combinedViewImage = [[[UIImage alloc] initWithCGImage:newImage] autorelease];
CGImageRelease(newImage);
CGImageRelease(overlayCopy);
CGContextRelease(context);
CGColorSpaceRelease(colorSpace);
return combinedViewImage;
}
+ (UIImage *) UIViewToImage:(UIView *)view withOverlayImage:(UIImage *)overlayImage
{
UIImage *viewImage = [ScreenCapture UIViewToImage:view]; // returns an autoreleased image
// Merge Image and Overlay
//
CGRect imageRect = CGRectMake((CGFloat)0.0, (CGFloat)0.0, viewImage.size.width*viewImage.scale, viewImage.size.height*viewImage.scale);
CGImageRef overlayCopy = CGImageCreateCopy( overlayImage.CGImage );
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef context = CGBitmapContextCreate(NULL, (int)viewImage.size.width*viewImage.scale, (int)viewImage.size.height*viewImage.scale, 8, (int)viewImage.size.width*4*viewImage.scale, colorSpace, kCGImageAlphaPremultipliedLast);
CGContextDrawImage(context, imageRect, viewImage.CGImage);
CGContextDrawImage(context, imageRect, overlayCopy);
CGImageRef newImage = CGBitmapContextCreateImage(context);
UIImage *combinedViewImage = [[[UIImage alloc] initWithCGImage:newImage] autorelease];
CGImageRelease(newImage);
CGImageRelease(overlayCopy);
CGContextRelease(context);
CGColorSpaceRelease(colorSpace);
return combinedViewImage;
}
+ (UIImage *) UIViewToImage:(UIView *)view
{
// Create a graphics context with the target size
// On iOS 4 and later, use UIGraphicsBeginImageContextWithOptions to take the scale into consideration
// On iOS prior to 4, fall back to use UIGraphicsBeginImageContext
//
// CGSize imageSize = [[UIScreen mainScreen] bounds].size;
CGSize imageSize = CGSizeMake( (CGFloat)480.0, (CGFloat)640.0 ); // camera image size
if (NULL != UIGraphicsBeginImageContextWithOptions)
UIGraphicsBeginImageContextWithOptions(imageSize, NO, 0);
else
UIGraphicsBeginImageContext(imageSize);
CGContextRef context = UIGraphicsGetCurrentContext();
// Start with the view...
//
CGContextSaveGState(context);
CGContextTranslateCTM(context, [view center].x, [view center].y);
CGContextConcatCTM(context, [view transform]);
CGContextTranslateCTM(context,-[view bounds].size.width * [[view layer] anchorPoint].x,-[view bounds].size.height * [[view layer] anchorPoint].y);
[[view layer] renderInContext:context];
CGContextRestoreGState(context);
// ...then repeat for every subview from back to front
//
for (UIView *subView in [view subviews])
{
if ( [subView respondsToSelector:#selector(screen)] )
if ( [(UIWindow *)subView screen] == [UIScreen mainScreen] )
continue;
CGContextSaveGState(context);
CGContextTranslateCTM(context, [subView center].x, [subView center].y);
CGContextConcatCTM(context, [subView transform]);
CGContextTranslateCTM(context,-[subView bounds].size.width * [[subView layer] anchorPoint].x,-[subView bounds].size.height * [[subView layer] anchorPoint].y);
[[subView layer] renderInContext:context];
CGContextRestoreGState(context);
}
UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); // autoreleased image
UIGraphicsEndImageContext();
return image;
}
+ (UIImage *) snapshot:(GLView *)eaglview
{
NSInteger x = 0;
NSInteger y = 0;
NSInteger width = [eaglview backingWidth];
NSInteger height = [eaglview backingHeight];
NSInteger dataLength = width * height * 4;
NSUInteger i;
for ( i=0; i<100; i++ )
{
glFlush();
CFRunLoopRunInMode(kCFRunLoopDefaultMode, (float)1.0/(float)60.0, FALSE);
}
GLubyte *data = (GLubyte*)malloc(dataLength * sizeof(GLubyte));
// Read pixel data from the framebuffer
//
glPixelStorei(GL_PACK_ALIGNMENT, 4);
glReadPixels(x, y, width, height, GL_RGBA, GL_UNSIGNED_BYTE, data);
// Create a CGImage with the pixel data
// If your OpenGL ES content is opaque, use kCGImageAlphaNoneSkipLast to ignore the alpha channel
// otherwise, use kCGImageAlphaPremultipliedLast
//
CGDataProviderRef ref = CGDataProviderCreateWithData(NULL, data, dataLength, NULL);
CGColorSpaceRef colorspace = CGColorSpaceCreateDeviceRGB();
CGImageRef iref = CGImageCreate(width, height, 8, 32, width * 4, colorspace, kCGBitmapByteOrder32Big | kCGImageAlphaPremultipliedLast, ref, NULL, true, kCGRenderingIntentDefault);
// OpenGL ES measures data in PIXELS
// Create a graphics context with the target size measured in POINTS
//
NSInteger widthInPoints;
NSInteger heightInPoints;
if (NULL != UIGraphicsBeginImageContextWithOptions)
{
// On iOS 4 and later, use UIGraphicsBeginImageContextWithOptions to take the scale into consideration
// Set the scale parameter to your OpenGL ES view's contentScaleFactor
// so that you get a high-resolution snapshot when its value is greater than 1.0
//
CGFloat scale = eaglview.contentScaleFactor;
widthInPoints = width / scale;
heightInPoints = height / scale;
UIGraphicsBeginImageContextWithOptions(CGSizeMake(widthInPoints, heightInPoints), NO, scale);
}
else
{
// On iOS prior to 4, fall back to use UIGraphicsBeginImageContext
//
widthInPoints = width;
heightInPoints = height;
UIGraphicsBeginImageContext(CGSizeMake(widthInPoints, heightInPoints));
}
CGContextRef cgcontext = UIGraphicsGetCurrentContext();
// UIKit coordinate system is upside down to GL/Quartz coordinate system
// Flip the CGImage by rendering it to the flipped bitmap context
// The size of the destination area is measured in POINTS
//
CGContextSetBlendMode(cgcontext, kCGBlendModeCopy);
CGContextDrawImage(cgcontext, CGRectMake(0.0, 0.0, widthInPoints, heightInPoints), iref);
// Retrieve the UIImage from the current context
UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); // autoreleased image
UIGraphicsEndImageContext();
// Clean up
free(data);
CFRelease(ref);
CFRelease(colorspace);
CGImageRelease(iref);
return image;
}
#end
I'm programming on objective-c. I have an image a line (see below) (1 x 30) pixels.
How can I get a UIImage (50 x 30) (see below) from this line?
Create a CGBitmapContext with size of 50 * 30 than you can just draw that image on the context by using CGContextDrawImage.
After that use CGBitmapContextCreateImage and [UIImage imageWithCGImage:] to create the UIImage
CGContextRef CreateBitmapContext(int pixelsWide, int pixelsHigh)
{
CGContextRef context = NULL;
CGColorSpaceRef colorSpace;
int bitmapByteCount;
int bitmapBytesPerRow;
bitmapBytesPerRow = (pixelsWide * 4); // RGBA
bitmapByteCount = (bitmapBytesPerRow * pixelsHigh);
colorSpace = CGColorSpaceCreateDeviceRGB();
context = CGBitmapContextCreate (NULL,
pixelsWide,
pixelsHigh,
8, // bits per component
bitmapBytesPerRow,
colorSpace,
kCGImageAlphaPremultipliedLast);
NSCAssert(context != NULL, #"cannot create bitmap context");
CGColorSpaceRelease( colorSpace );
return context;
}
CGContextRef context = CreateBitmapContext(50, 30);
UIImage *yourLineImage = ...;
CGImageRef cgImg = [yourLineImage CGImage];
for (int i = 0; i < 50; i++) {
CGRect rect;
rect.origin.x = i;
rect.origin.y = 0;
rect.size.width = 1;
rect.size.height = 30;
CGContextDrawImage(context, rect, cgImg);
}
CGImageRef output = CGBitmapContextCreateImage(context);
UIImage *result = [UIImage imageWithCGImage:output];
if your line has simple color, try this lazy method:
UIImageView *line = [[UIImageView alloc] initWithFrame:CGRectMake(10, 10, 50, 30)];
[line setImage:[UIImage imageNamed:#"your gray line"]];
[self.view addSubView:line];
You can use +[UIColor colorWithPatternImage] in iOS:
NSString *path =
[[NSBundle mainBundle] pathForResource:#"<# the pattern file #>" ofType:#"png"];
UIColor *patternColor = [UIColor colorWithPatternImage:
[UIImage imageWithContentsOfFile:path]];
/* use patternColor anywhere as a regular UIColor instance */
It works better with seamless patterns. For OSX you can use +[NSColor colorWithPatternImage] method.
If you just want to draw the image, you might want to try UIImage's drawInRect: method.
You'd typically want to call this from your custom UIView's drawRect:.
There are different approaches to drawing in Cocoa (and Cocoa-Touch) so here's Apple's Drawing and Printing Guide for iOS.
After applying a 3d transform to an UIImageView.layer, I need to save the resulting "view" as a new UIImage... Seemed like a simple task at first :-) but no luck so far, and searching hasn't turned up any clues :-( so I'm hoping someone will be kind enough to point me in the right direction.
A very simple iPhone project is available here.
Thanks.
- (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);
}
I ended up creating a render method pixel per pixel on the CPU using the inverse of the view transform.
Basically, it renders the original UIImageView into a UIImage. Then every pixel in the UIImage is multiplied by the inverse transform matrix to generate the transformed 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
Theoretically, you could use the (now-allowed) undocumented call UIGetScreenImage() after quickly rendering it to the screen on a black background, but in practice this will be slow and ugly, so don't use it ;P.
I have the same problem with you and I found the solution!
I want to rotate the UIImageView, because I will have the animation.
And save the image, I use this method:
void CGContextConcatCTM(CGContextRef c, CGAffineTransform transform)
the transform param is the transform of your UIImageView!. So anything you have done to the imageView will be the same with image!.
And I have write a category method of 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;
}
Hope this will help you.
Have you had a look at this? UIImage from UIView
I had the same problem, I was able to use UIView's drawViewHierarchyInRect:afterScreenUpdates: method, from iOS 7.0 -
(Documentation)
It draws the whole tree as it appears on the screen.
UIGraphicsBeginImageContextWithOptions(viewToRender.bounds.size, YES, 0);
[viewToRender drawViewHierarchyInRect:viewToRender.bounds afterScreenUpdates:YES];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
Let say you have and UIImageView called imageView.
If you apply 3d transform and try to render this view with UIGraphicsImageRenderer transforms are ignored.
imageView.layer.transform = someTransform3d
but if you convert CATransform3d to CGAffine transform using CATransform3DGetAffineTransform and apply it to transform property of image view, it works.
imageView.transform = CATransform3DGetAffineTransform(someTransform3d)
And then, you can use the extension below to save it as UIImage
extension UIView {
func asImage() -> UIImage {
let renderer = UIGraphicsImageRenderer(bounds: bounds)
return renderer.image { rendererContext in
layer.render(in: rendererContext.cgContext)
}
}
}
And just call
let image = imageView.asImage()
In your captureView: method, try replacing this line:
[view.layer renderInContext:UIGraphicsGetCurrentContext()];
with this:
[view.layer.superlayer renderInContext:UIGraphicsGetCurrentContext()];
You may have to adjust the size you use to create the image context.
I don't see anything in the API doc that says renderInContext: ignores 3D transformations. However, the transformations apply to the layer, not its contents, which is why you need to render the superlayer to see the transformation applied.
Note that calling drawRect: on the superview definitely won't work, as drawRect: does not draw subviews.
3D transform on UIImage / CGImageRef
I've improved Marcos Fuentes answer. You should be able to calculate the mapping of each pixel yourself.. Not perfect, but it does the trick...
It is available on this repository http://github.com/hfossli/AGGeometryKit/
The interesting files is
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
3D transform on UIView / UIImageView
https://stackoverflow.com/a/12820877/202451
Then you will have full control over each point in the quadrilateral. :)
A solution I found that at least worked in my case was to subclass CALayer. When a renderInContext: message is sent to a layer, that layer automatically forwards that message to all its sublayers. So all I had to do was to subclass CALayer and override the renderInContext: method and render what I needed to be rendered in the provided context.
For example, in my code I had a layer for which I was setting its contents to an image of an arrow:
UIImage *image = [UIImage imageNamed:#"arrow.png"];
MYLayer *myLayer = [[CALayer alloc] init];
[myLayer setContents:(__bridge id)[image CGImage]];
[self.mainLayer addSublayer:myLayer];
Now when I was applying a 3D 180 degree rotation over the Y-axis on the arrow and was trying to do a [self.mainLayer renderInContext:context] afterwards I was still getting the un-rotated image.
So in my subclass MyLayer I overrode renderInContext: and used an already rotated image to draw in provided context:
- (void)renderInContext:(CGContextRef)ctx
{
NSLog(#"Rendered in context");
UIImage *image = [UIImage imageNamed:#"arrow_rotated.png"];
CGContextDrawImage(ctx, self.bounds, image.CGImage);
}
This worked in my case, however I can see that if you are doing lots of 3D transforms you may not be able to have an image ready for every possible scenario. In many other cases though it should be possible to render the result of 3D transform using 2D transforms in the passed context. For example in my case instead of using a different image arrow_rotated.png I could use the arrow.png image and mirror it and draw it in the context.