CALayer sublayerTransform (CATransform3D) - how can I move "camera"? - core-animation

Attempt A
this code
CATransform3D perspectiveTransform = CATransform3DIdentity;
perspectiveTransform.m34 = -1.0 / 1000.0;
perspectiveTransform = CATransform3DRotate(perspectiveTransform, angle / 2.0, 0.0, 1.0, 0.0);
self.sublayerTransform = perspectiveTransform;
gives this result
The red frame is the background color of the layer
Attempt B
This code
CATransform3D perspectiveTransform = CATransform3DIdentity;
perspectiveTransform.m34 = -1.0 / 1000.0;
// following line is added
perspectiveTransform = CATransform3DTranslate(perspectiveTransform, -width / 2.0, 0, 0);
perspectiveTransform = CATransform3DRotate(perspectiveTransform, angle / 2.0, 0.0, 1.0, 0.0);
self.sublayerTransform = perspectiveTransform;
gives this result
The red frame is the background color of the layer
So what is wrong?
In the rendered example from "Attempt A" is seen from left center and therefore the fold is seen from the wrong angle (left fold gets slightly thinner than right fold)
By translating it on the x-axis, as in "Attempt B", I am able to get it rendered correctly, but then occurs another mistake: the content is now out of bounds (red rectangle is bounds).
How can I make the camera be from the middle?

Well, it seems like changing the anchorpoint is the easiest. I just need to change some other transforms (not revealed in post) to make it work.

Related

CGContextDrawLinearGradient confusion. Need clarification

I am have been experimenting with CGContextDrawLinearGradient and I terribly confused with what start point and end point mean? I thought they mean coordinates on the current CGContext so if I define start point to be 0,0 and end point to be 100,100, i would get a square with gradient. I get something else altogether that I just cannot connect to my co-ordinates.
This is the code that I have:
- (void)drawRect:(CGRect)rect {
// Drawing code
CGContextRef current_context = UIGraphicsGetCurrentContext();
CGContextSaveGState(current_context);
// Gradient
CGFloat locations[3] = {0.0, 0.5, 1.0};
CGFloat components[12] = {1.0, 0.0, 0.0, 1.0, 1.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0};
CGColorSpaceRef colorspace = CGColorSpaceCreateDeviceRGB();
CGGradientRef gradient = CGGradientCreateWithColorComponents(colorspace, components, locations, 3);
CGPoint startPoint = CGPointMake(0, 0);
CGPoint endPoint = CGPointMake(40, 40);
CGContextDrawLinearGradient(current_context, gradient, startPoint, endPoint, 0);
// Shadow
CGContextSetShadow(current_context, CGSizeMake(4,7), 1.0);
// Image
UIImage *logoImage = [UIImage imageNamed:#"logo.png"];
[logoImage drawInRect:bounds];
CGContextRestoreGState(current_context);
}
Thanks for your help in advance..
Most of your code is quite OK; the problem occurs in the following lines:
CGPoint startPoint = CGPointMake(0, 0);
CGPoint endPoint = CGPointMake(40, 40);
CGContextDrawLinearGradient(current_context, gradient, startPoint, endPoint, 0);
CGContextDrawLinearGradient expects to get a start and an end point (defining a line, not two diagonal edges of a square!).
The gradient is then drawn by colored lines perpendicular to this controlling line. The drawing starts with a line going through startPoint (perpendicular to the line between startPoint and endPoint) using the start color (color at location 0). The next line is drawn through a point 'one pixel' closer to the endPoint, with a color calculated to be somewhere between the start and the end or next color (depending of the number of color locations). Finally a line is drawn through the endPoint (again perpendicular...) using the end color.
The advantage of using a (controlling) line instead of a square is, that the gradient can be drawn in any direction; horizontally, vertically, somewhere between, only depending of the direction of the given line.
In your example code, the gradient should be diagonally, as your line has an angle of 45° to the x-axis :-).
code from raywanderlich tutorial
override func drawRect(rect: CGRect) {
//2 - get the current context
let context = UIGraphicsGetCurrentContext()
let colors = [startColor.CGColor, endColor.CGColor]
//3 - set up the color space
let colorSpace = CGColorSpaceCreateDeviceRGB()
//4 - set up the color stops
let colorLocations:[CGFloat] = [0.0, 1.0]
//5 - create the gradient
let gradient = CGGradientCreateWithColors(colorSpace,
colors,
colorLocations)
//6 - draw the gradient
var startPoint = CGPoint.zeroPoint
var endPoint = CGPoint(x:0, y:self.bounds.height)
CGContextDrawLinearGradient(context,
gradient,
startPoint,
endPoint,
0)
}

OpenGL ES 2.0, quad does not change colour when background is changed

I have drawn a circle in a quad on OpenGL ES 2.0. The code in the fragment shader takes the centre and radius that has been set and creates a circle within the quad. This worked fine until I tried to change the colour of the background as the quad still shows up blank and does not get filled with the background colour/texture. Is there an easy way to make the quad fill with the same colour/texture as the background whilst also keeping the circle on show?
The code in the fragment shader is as follows:
"varying highp vec2 textureCoordinate;\n"
"const highp vec2 center = vec2(0.5, 0.5);\n"
"const highp float radius = 0.5;\n"
"void main()\n"
"{\n"
"highp float distanceFromCenter = distance(center, textureCoordinate);\n"
"lowp float checkForPresenceWithinCircle = step(distanceFromCenter, radius);\n"
"gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0) * checkForPresenceWithinCircle;\n"
"}\n"
The trick is not to fill the quad with the background, but to avoid replacing it outside the area covered by the circle.
Your fragment shader will always output a value - even if it's outside the circle. That is, if checkForPresenceWithinCircle is 0.0, gl_FragColor gets assigned vec4(0.0, 0.0, 0.0, 0.0) - transparent black.
I think what you are looking for is the discard keyword, which prevents the shader from outputting anything for that fragment. Something like:
if ( checkForPresenceWithinCircle > 0.0 )
gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
else
discard;
Since you know that the alpha will be 0.0 outside the circle and 1.0 within, you could also achieve the same effect using alpha blending from the API-side:
draw_background();
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
draw_circle();
glDisable(GL_BLEND);
The alpha part of gl_FragColor is normally read from the texture, like this:
vec4 vTexture = texture2D(gsuTexture0, gsvTexCoord);
gl_FragColor = vTexture;
Also, be sure your color buffer clear has the alpha set to 0.0, like this:
glClearColor(fRed, fGreen, fBlue, 0.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
And there is also the problem that you can't use alpha textures with the Android Bitmap class as discussed here:
http://software.intel.com/en-us/articles/porting-opengl-games-to-android-on-intel-atom-processors-part-1/

iOS CATransform3D Coordinates

Would really appreciate any help on this one. I have applied a 3D transformation on a view and need to identify the edge coordinates of the rendered view so I can present another view adjacent to it (without any pixels gap). Specifically I want a series of views ("pages") to fold-up like a leaflet, by animating the angle.
int dir = (isOddNumberedPage ? 1 : -1);
float angle = 10.0;
theView.frame = CGRectMake(pageNumber * 320, 0, 320, 460);
CATransform3D rotationAndPerspectiveTransform = CATransform3DIdentity;
rotationAndPerspectiveTransform.m34 = -1.0 / 2000; // Perspective
rotationAndPerspectiveTransform = CATransform3DRotate(rotationAndPerspectiveTransform,
dir * angle / (180.0 / M_PI), 0.0f, 1.0f, 0.0f);
theView.layer.transform = rotationAndPerspectiveTransform;
// Now need to get the top, left, width, height of the transformed view to correct the view's left offset
I have tried a number of ways of doing this by inspecting the CALayer, a failed attempt at using some matrix maths code snippets I found, but have not been able to crack it or even get close (depending on angle, a good 20 pixels out). Is there a way I can do this without spending 2 weeks reading a matrix maths textbook?
The frame of a view is an axis-aligned rectangle in the superview's coordinate system. The frame fully encloses the view's bounds. If the view is transformed, the frame adjusts to tightly enclose the view's new bounds.
When you apply a Y-axis rotation and perspective to a view, the left and right edges of the view move toward its anchor point (which is normally the center of the view). The left edge also grows either taller or shorter, and the right edge does the opposite.
So the frame of the view (after applying the transformation) will give you the left edge coordinate and the width of the transformed view, and the top and height of the taller edge (which might be either the left or right edge). Here's my test code:
NSLog(#"frame before tilting = %#", NSStringFromCGRect(self.tiltView.frame));
float angle = 30.0;
CATransform3D rotationAndPerspectiveTransform = CATransform3DIdentity;
rotationAndPerspectiveTransform.m34 = -1.0 / 2000; // Perspective
rotationAndPerspectiveTransform = CATransform3DRotate(rotationAndPerspectiveTransform,
1 * angle / (180.0 / M_PI), 0.0f, 1.0f, 0.0f);
self.tiltView.layer.transform = rotationAndPerspectiveTransform;
NSLog(#"frame after tilting = %#", NSStringFromCGRect(self.tiltView.frame));
Here's the output:
2012-01-04 12:44:08.405 layer[72495:f803] frame before tilting = {{50, 50}, {220, 360}}
2012-01-04 12:44:08.406 layer[72495:f803] frame after tilting = {{62.0434, 44.91}, {190.67, 370.18}}
You can also get the coordinates of the corners of the view, in the superview's coordinate space using convertPoint:fromView: or convertPoint:toView:. Test code:
CGRect bounds = self.tiltView.bounds;
CGPoint upperLeft = bounds.origin;
CGPoint upperRight = CGPointMake(CGRectGetMaxX(bounds), bounds.origin.y);
CGPoint lowerLeft = CGPointMake(bounds.origin.x, CGRectGetMaxY(bounds));
CGPoint lowerRight = CGPointMake(upperRight.x, lowerLeft.y);
#define LogPoint(P) NSLog(#"%s = %# -> %#", #P, \
NSStringFromCGPoint(P), \
NSStringFromCGPoint([self.tiltView.superview convertPoint:P fromView:self.tiltView]))
LogPoint(upperLeft);
LogPoint(upperRight);
LogPoint(lowerLeft);
LogPoint(lowerRight);
Output:
2012-01-04 13:03:00.663 layer[72635:f803] upperLeft = {0, 0} -> {62.0434, 44.91}
2012-01-04 13:03:00.663 layer[72635:f803] upperRight = {220, 0} -> {252.713, 54.8175}
2012-01-04 13:03:00.663 layer[72635:f803] lowerLeft = {0, 360} -> {62.0434, 415.09}
2012-01-04 13:03:00.663 layer[72635:f803] lowerRight = {220, 360} -> {252.713, 405.182}
Notice that the Y coordinates of the upperLeft and upperRight points are different in the superview's coordinate system.

Is a Right-to-Left progress bar possible on iOS?

I've tried sending [UIProgressView setProgress] negative values, and that doesn't work.
Is there some other way to get a progress bar that fills from the right-hand end?
You could try setting the transform property of your UIProgressView to a new CGAffineTransform that rotates the view by 180 degrees and flips it vertically (to preserve the "shininess") (see CGAffineTransformMake() and CGAffineTransformRotate()).
Something along the lines of:
UIProgressView *pv = [[UIProgressView alloc] initWithProgressViewStyle:UIProgressViewStyleBar];
pv.frame = CGRectMake(10, 100, 300, 11);
CGAffineTransform transform = CGAffineTransformMake(1, 0, 0, -1, 0, pv.frame.size.height); // Flip view vertically
transform = CGAffineTransformRotate(transform, M_PI); //Rotation angle is in radians
pv.transform = transform;
pv.progress = 0.5;
You can rotate the UIProgressView:
progressView.transform = CGAffineTransformMakeRotation(DegreesToRadians(180));
where DegreesToRadians is:
#define DegreesToRadians(d) ((d) * M_PI / 180.0)
To change the progress value, use positive numbers.
A simpler version is to flip it horizontally.
progressView.transform = CGAffineTransformMakeScale(-1.0f, 1.0f);
In iOS 9+, you can use semanticContentAttribute:
progressView.semanticContentAttribute = .forceRightToLeft
You can rotate the view by 180°:
progressView.transform = CGAffineTransformMakeRotation(-M_PI);
Swift answer:
progressView.transform = CGAffineTransform(rotationAngle: .pi)
In iOS 7 with storyboards, you can set the Progress Tint to the Track Tint and vice versa, then subtract the regular value from one and set that to the current progress. Probably better to do it the other (rotation) way, but I thought I would throw this out there.
Swift 5 Version
progressView.transform = CGAffineTransform(scaleX: -1.0, y: 1.0)

Linear gradient aliasing with CoreGraphics

I'm trying to emulate the color tint effect from the UITabBarItem.
When I draw a linear gradient at an angle, I get visible aliasing in the middle part of the gradient where the two colors meet at the same location. Left is UITabBarItem, right is my gradient with visible aliasing (stepping):
Here is the snippet of relevant code:
UIGraphicsBeginImageContextWithOptions(size, NO, 0.0);
CGContextRef c = UIGraphicsGetCurrentContext();
CGContextSaveGState(c);
CGContextScaleCTM(c, 1.0, -1.0);
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGFloat components[16] = {1,1,1,1,
109.0/255.0,175.0/255.0,246.0/255.0,1,
31.0/255.0,133.0/255.0,242.0/255.0,1,
143.0/255.0,194.0/255.0,248.0/255.0,1};
CGFloat locations[4] = {0.0, 0.62, 0.62, 1};
CGGradientRef colorGradient =
CGGradientCreateWithColorComponents(colorSpace, components,
locations, (size_t)4);
CGContextDrawLinearGradient(c, colorGradient, CGPointZero,
CGPointMake(size.width*1.0/3.9, -size.height),0);
CGGradientRelease(colorGradient);
CGColorSpaceRelease(colorSpace);
CGContextRestoreGState(c);
UIImage *resultImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return resultImage;
What do I need to change, to get a smooth angled gradient like in UITabBarItem?
What is the interpolation quality of your context set to? CGContextGetInterpolationQuality()/CGContextSetInterpolationQuality(). Try changing that if it's too low.
If that doesn't work, I'm curious what happens if you draw the gradient vertically (0,Ymin)-(0,Ymax) but apply a rotation transformation to your context...
As a current workaround, I draw the gradient at double resolution into an image and then draw the image with original dimensions. The image scaling that occurs, takes care of the aliasing. At the pixel level the result is not as smooth as in the UITabBarItem, but that probably uses an image created in Photoshop or something similar.