iPhone skew a CALayer - iphone

I'm a beginner and I am doing some exercises to familiarize myself with CALayer ...
I just want to know how to "incline" (or skew) a CALayer 45° angle ?
Thank you.

CALayers have a property, affineTransform that takes a CAAffineTransform. That documentation explicitly notes that:
Scaling, rotation, and translation are
the most commonly used manipulations
supported by affine transforms, but
skewing is also possible.
(emphasis mine, obviously)
There's no built in helper to construct a skew transform, but you could do something like (untested):
CGAffineTransform CGAffineTransformMakeSkew(CGFloat skewAmount)
{
CGAffineTransform skewTransform = CGAffineTransformIdentity;
skewTransform.b = skewAmount;
return skewTransform;
}
Then, for a skew such that things that were verticals stand at 45 degrees to the horizontal you'd use:
layer.affineTransform = CGAffineTransformMakeSkew(1.0f);

CALayers can be transformed using matrix operations. The skew transformation is represented by the following matrix
So if you want to do a skew transformation along the x axis you can use the following sample.
CALayer* layer = [CALayer layer];
layer.frame = CGRectMake(50,50,50,50);
layer.backgroundColor = [UIColor blueColor].CGColor;
[self.window.layer addSublayer:layer];
float theta = -45.0f;
CGAffineTransform t = CGAffineTransformIdentity;
t.b = tan(theta*M_PI/180.0f);
layer.transform = CATransform3DMakeAffineTransform(t);
The following sample will result in a layer that looks like the following

You could do this but you would have to mess with the layer's transform property, which is a struct CATransform3D. You're going to have to do some vector math to do this, as you . See the compute_transform_matrix(...) function from this answer for more details.
You'll want to do something like this:
CGRect r = layer.bounds;
layer.transform = compute_transform_matrix(r.origin.x, r.origin.y, r.size.width, r.size.height,
r.size.height, r.origin.y, r.size.width + r.size.height, r.origin.y,
r.origin.x, r.origin.y + r.size.height, r.size.width, r.origin.y );
Check my math on this. It should be right.

There are a lot of resources on how transforms work and it takes a bit of time to really understand them (at least for me!), here is some straight-to-the-point code that makes sure to indicate to which axis the transform is applied.
/*!
* Positive `skewX` creates a shift to the left.
* Negative `skewX` creates a shift to the right.
*
* `skewX` is NOT pixel based. Test values of 0.0f - 1.0f.
*/
CGAffineTransform CGAffineTransformMakeSkewX(CGFloat skewX)
{
return CGAffineTransformMakeSkew(skewX, 0.0f);
}
/*!
* Positive `skewY` creates a shift to the bottom.
* Negative `skewY` creates a shift to the top.
*
* `skewY` is NOT pixel based. Test values of 0.0f - 1.0f.
*/
CGAffineTransform CGAffineTransformMakeSkewY(CGFloat skewY)
{
return CGAffineTransformMakeSkew(0.0f, skewY);
}
/*!
* Positive `skewX` creates a shift to the left.
* Negative `skewX` creates a shift to the right.
*
* Positive `skewY` creates a shift to the bottom.
* Negative `skewY` creates a shift to the top.
*
* The skew values are NOT pixel based. Test values of 0.0f - 1.0f.
*/
CGAffineTransform CGAffineTransformMakeSkew(CGFloat skewX, CGFloat skewY)
{
return CGAffineTransformMake(1.0f, skewY, skewX, 1.0f, 0.0f, 0.0f);
}

Related

draw function causes app to lag

I'm using a draw function to highlight the score inserted into a table of highscores but i find that it makes my app lag when i try to leave the highscore layer. I'm still relatively new to cocos2d so I was wondering if there is a better way so it doesn't cause any lag. I find if i comment out this function that the isn't any lag. Heres my code:
- (void)draw {
[super draw];
if(currentScorePosition < 0 || currentScore==0) return;
float w = 320.0f;
float h = 20.0f;
float x = (320.0f - w) / 2.0f;
float y = 230.0f - currentScorePosition * h;
CGPoint vertices[4];
vertices[0] = ccp(x, y);
vertices[1] = ccp(x+w, y);
vertices[2] = ccp(x+w, y+h);
vertices[3] = ccp(x, y+h);
CCDrawNode *draw = [[[CCDrawNode alloc] init] autorelease];
[draw drawPolyWithVerts:vertices count:4 fillColor:ccc4f(0.5, 0.5, 0.8, 0.5) borderWidth:2.0 borderColor:ccc4f(0.0, 0.0, 0.0, 0.0)];
[self addChild:draw z:0 ];
}
You're creating a new CCDrawNode every frame. Over time this will slow down the game as it has to draw more and more draw nodes.
Solution: create one draw node up front and add it as child. Keep a reference to it in an ivar. Perform drawing with just this single draw node.
Note that the draw methods of CCDrawNode are still additive. If you want to draw just this one polygon and update it over time, then you'll have to call clear before drawing:
[theDrawNode clear];
[theDrawNode drawPolyWithVerts:vertices
count:4
fillColor:ccc4f(0.5, 0.5, 0.8, 0.5)
borderWidth:2.0
borderColor:ccc4f(0.0, 0.0, 0.0, 0.0)];
Another note: you can use the draw node outside the draw method. In fact if you run the code like you did, the draw node won't be drawn until the next frame and thus it'll always lag 1 frame behind. Use a scheduled update method to update the draw node.

CATransform3D - go back to previous state

Hi I`m working with CATransform3D.
Every time that I call the following method with the variable radian the UIImageView is rotated “radian * M_PI” from the previous angle.
I trying to go back to the original state. There is any way to do that?
I mind I want to sent a radian value i.e. 10 and that the view maintain these angle if is called more than once.
METHOD:
CATransform3D rotatedTransform = self.needleB.layer.transform;
rotatedTransform = CATransform3DRotate(rotatedTransform, radian * M_PI / 180.0, 0.0f, 0.0f, 1.0f);
self.needleB.layer.transform = rotatedTransform;
self.needleB.layer.anchorPoint = CGPointMake(0.5, 0.9);
self.needleB.center = CGPointMake(160, 250);
Thanks !!
To undo all transforms, assign the transform property of your layer to CATransform3DIdentity.

Objective C: Using UIImage for Stroking

I am trying to apply a texture for my brush but i'm really having a hard time figuring how it is done.
Here's the image of my output.
I used an UIImage that just follows the touch on the screen but when i swipe it faster the result is on the right side "W", and on the left side that's the result when i swipe it slow.
i tried using the CGContextMoveToPoint and CGContextAddLineToPoint i don't know how apply the texture.
Is it possible to use UIImage for the stroke texture?
Here's my code
UIImage * brushImageTexture = [UIImage imageNamed:#"brushImage.png"];
[brushImagetexture drawAtPoint:CGPointMake(touchCurrentPosition.x, touchVurrentPosition.y) blendMode:blendMode alpha:1.0f];
You need to manually draw the image at each point along the line from the previous point to the current point.
CGPoint vector = CGPointMake(currentPosition.x - lastPosition.x, currentPosition.y - lastPosition.y);
CGFloat distance = hypotf(vector.x, vector.y);
vector.x /= distance;
vector.y /= distance;
for (CGFloat i = 0; i < distance; i += 1.0f) {
CGPoint p = CGPointMake(lastPosition.x + i * vector.x, lastPosition.y + i * vector.y);
[brushImageTexture drawAtPoint:p blendMode:blendMode alpha:1.0f];
}

Getting the scale factor from a CABasicAnimation

I'm animating a shrinking object. At any point the user can hit a button to get the current scale factor of the object. (I start by scaling the object up using a CGAffineTransformMakeScale, so the scale factor should be 1 when it reaches its original size). I'm just not sure how to retrieve the current scale factor from the animation UIImageView... Here's my code:
- (void)startShrink {
CGAffineTransform scaleFactor = CGAffineTransformMakeScale(kScaleX, kScaleY);
imageOutline.transform = scaleFactor;
CABasicAnimation *shrink = [CABasicAnimation animationWithKeyPath:#"transform.scale"];
shrink.toValue = [NSNumber numberWithDouble:0.5];
shrink.duration = 2.0;
shrink.fillMode=kCAFillModeForwards;
shrink.removedOnCompletion=NO;
shrink.delegate = self;
[imageOutline.layer addAnimation:shrink forKey:#"shrink"];
}
I tried the following, but I'm not sure m12 is the value I should be retrieving from the transform. Or if in fact this is the right approach:
- (float)calculateScale {
CATransform3D scaleTransform = [(CALayer *)[imageOutline.layer presentationLayer] transform];
float scale = scaleTransform.m12;
NSLog(#"Scale: %g", scale);
return scale;
}
Any advice most appreciated :)
Thanks, Michael.
If you just scale your view uniformly on all axes then scale value should equal to m11 (and m22 as well).
I just ran a sample with your code copy-pasted - it seems that the following 2 lines are redundant:
CGAffineTransform scaleFactor = CGAffineTransformMakeScale(kScaleX, kScaleY);
imageOutline.transform = scaleFactor;
View immediately shrinks by half (I used 0.5 for scale values) and then shrinks twice more with animation

Best way to accomplish this drawing with Quartz 2D / Core Graphics?

As the background for one of the views in my app, I'd like to draw a fairly simple rectangular border just inside its frame. This would essentially be a rectangular gradient: a black line around the frame, fading to white about 10-20 pixels in. Unfortunately, as far as I can tell, Core Graphics doesn't provide rectangular gradients (either with CGGradient or CGShading). So I'm wondering what the best approach would be.
Two that occur to me:
Draw a series of concentric rectangles, each subsequent one lighter in color, and inset by 1px on each side. I can't think of a simpler approach, but I have to do all of the gradient calculations myself, and it might be a lot of graphics operations.
Use CGGradient in linear mode, once for each side. But for this to work, I think I'd need to set up a trapezoidal clipping area for each side first, so that the gradients would be mitered at the corners.
Seems like there should be a way to use path stroking to do this, but it doesn't seem like there's a way to define a pattern that's oriented differently on each side.
I would go with option #2:
Use CGGradient in linear mode, once for each side. But for this to work, I think I'd need to set up a trapezoidal clipping area for each side first, so that the gradients would be mitered at the corners.
Using NSBezierPath to create the trapezoidal regions would be fairly straightforward, and you would only have to perform four drawing operations.
Here's the basic code for creating the left side trapezoidal region:
NSRect outer = [self bounds];
NSPoint outerPoint[4];
outerPoint[0] = NSMakePoint(0, 0);
outerPoint[1] = NSMakePoint(0, outer.size.height);
outerPoint[2] = NSMakePoint(outer.size.width, outer.size.height);
outerPoint[3] = NSMakePoint(outer.size.width, 0);
NSRect inner = NSInsetRect([self bounds], borderSize, borderSize);
NSPoint innerPoint[4];
innerPoint[0] = inner.origin;
innerPoint[1] = NSMakePoint(inner.origin.x,
inner.origin.y + inner.size.height);
innerPoint[2] = NSMakePoint(inner.origin.x + inner.size.width,
inner.origin.y + inner.size.height);
innerPoint[3] = NSMakePoint(inner.origin.x + inner.size.width,
inner.origin.y);
NSBezierPath leftSidePath = [[NSBezierPath bezierPath] retain];
[leftSidePath moveToPoint:outerPoint[0]];
[leftSidePath lineToPoint:outerPoint[1]];
[leftSidePath lineToPoint:innerPoint[1]];
[leftSidePath lineToPoint:innerPoint[0]];
[leftSidePath lineToPoint:outerPoint[0]];
// ... etc.
[leftSidePath release];
something like this could also work.
basically: instead of using clipping paths, simply use blendmode.
and in this example the gradient is cached in a CGLayer.
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGColorSpaceRef cspace = CGColorSpaceCreateDeviceRGB();
CGContextSetRGBFillColor(ctx, 1.0, 1.0, 1.0, 1.0);
CGContextFillRect(ctx,self.bounds);
CGFloat w = self.bounds.size.width;
CGFloat h = self.bounds.size.height;
CGFloat dh = (w-h)/2;
CGLayerRef l = CGLayerCreateWithContext(ctx,CGSizeMake(h,48.0f),NULL);
CGContextRef lctx = CGLayerGetContext(l);
float comp[] = { .2,.5,1.0,1.0,1.0,1.0,1.0,1.0};
CGGradientRef gradient = CGGradientCreateWithColorComponents(cspace, comp, NULL, 2);
CGContextDrawLinearGradient(lctx, gradient,CGPointMake(0,0),CGPointMake(0,48), 0);
CGContextSaveGState(ctx);
CGContextSetBlendMode(ctx,kCGBlendModeDarken);
for(int n=1;n<5;n++)
{
CGContextTranslateCTM(ctx,w/2.0,h/2.0);
CGContextRotateCTM(ctx, M_PI_2);
CGContextTranslateCTM(ctx,-w/2.0,-h/2.0);
CGContextDrawLayerAtPoint(ctx,CGPointMake((n%2)*dh,(n%2)*-dh),l);
}
CGContextRestoreGState(ctx);