i have the following situation , i'm trying to make a photo editor and i need to do scale,translate and rotate operations for an image with around center of view
the problem is that if i apply translate after 90 degree translate left is translate top and the anchor point is not the center of the view after translation
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
// ImageView
v = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 768, 500)];
[v setBackgroundColor:[UIColor redColor]];
// ImageView's Image
UIImage *img = [UIImage imageNamed:#"_my.jpg"];
[v setImage:img];
v.contentMode = UIViewContentModeScaleAspectFit;
v.layer.anchorPoint = CGPointMake(0.5, 0.5);
[self.view setBackgroundColor:[UIColor clearColor]];
// UiView containing ImageView
vc = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 768, 500)];
[vc addSubview:v];
[self.view addSubview:vc];
}
- (IBAction)Click:(id)sender {
// Translate
if ([sender tag] == 1) {
CGAffineTransform t0 = v.transform;
CGAffineTransform t1 = CGAffineTransformTranslate(t0, 10.0, 0.0);
v.transform = t1;
}
// Rotate
if ([sender tag] == 2) {
CGAffineTransform t0 = v.transform;
CGAffineTransform t1 = CGAffineTransformRotate(t0, DEGREES_TO_RADIANS(10));
v.transform = t1;
}
}
does anyone know how can i keep the center of view as the anchor point and translate after rotation to work properly ?
tx
Transformations with Origin
However, especially in 2D, you frequently need to add an origin displacement to your transformation. That's usually added to the beggining, and must be negated too, so:
// World Matrix with Origin
-OriginTranslation * Scale * Rotation * PositionTranslation
The same thing can also be done with a view matrix, but you add it to the end and this time you don't negate it:
// View Matrix with Origin
-PositionTranslation * -Rotation * Zoom * OriginTranslation
Transformations with Selective Origin
There are also some cases where you want to have an origin displacement, but you want it to affect only your scale and rotation, but not your translation. The way to handle that is to undo the origin transformation before reaching the translation component. For example, with the world matrix:
I don't remember, exactly, how transformation is applying for view, one from those should be work.
// World Matrix with Origin for Scale and Rotation
-OriginTranslation * Scale * Rotation * OriginTranslation * PositionTranslation
And the view matrix:
// View Matrix with Origin
-PositionTranslation * -OriginTranslation * -Rotation * Zoom * OriginTranslation
Example of transformations without origin, order: scale * rotation * translation
Related
It's been a while, as I was hospitalized for 3 months after a motorcycle accident.
So I just got to renew my apple programming subscription :-)
I have another question that has been on my mind for quite some time.
In my iPad application I draw a triangle in the center of an iPad like this:
- (void)initTriangle
{
CGRect screenBound = [[UIScreen mainScreen] bounds];
CGSize screenSize = screenBound.size;
CGFloat screenWidth = screenSize.width;
CGFloat screenHeight = screenSize.height;
// draw triangle (TRIANGLE)
CGMutablePathRef path = CGPathCreateMutable();
CGPathMoveToPoint(path,NULL, 0.5*screenWidth, 0.5*screenHeight-25);
CGPathAddLineToPoint(path, NULL, 0.5*screenWidth-25, 0.5*screenHeight+25);
CGPathAddLineToPoint(path, NULL, 0.5*screenWidth+25, 0.5*screenHeight+25);
CGPathAddLineToPoint(path, NULL, 0.5*screenWidth, 0.5*screenHeight-25);
CAShapeLayer *triangle = [CAShapeLayer layer];
[triangle setPath:path];
[triangle setFillColor:[[UIColor blackColor] CGColor]];
[[[self view] layer] addSublayer:triangle];
CGPathRelease(path);
}
And I call this from my viewDidLoad like this:
[self initTriangle];
Now I'm trying to rotate this triangle with the rotation of my iPad around Z-Axis while laying flat on the table. I have a function that gives me the yaw readings in float and I'm calling my
-(void)updateTriangleWithYaw:(float)yaw
method, but I don't know what to exactly put in there to make it rotate.
here is what my method looks like so far:
-(void)updateTriangleWithYaw:(float)yaw
{
CGRect screenBound = [[UIScreen mainScreen] bounds];
CGSize screenSize = screenBound.size;
CGFloat screenWidth = screenSize.width;
CGFloat screenHeight = screenSize.height;
NSLog(#"YAW: %f", yaw);
Z += 2 * yaw;
Z *= 0.8;
CGFloat newR = R + 10 * yaw;
self.triangle.frame = CGRectMake(0.5*screenWidth, 0.5*screenHeight, newR, newR);
}
Any help will be greatly appreciated!
Thanks and be safe guys!!
You should set the layer's affineTransform. You can apply a rotation transform like:
[self.triangle setAffineTransform:CGAffineTransformMakeRotation(yaw)];
This method, setAffineTransform is a convenience to set the transform property of the layer, which is a more general type of transform CATransform3D. You can also set the transform of the layer directly, and if you want to do that you can make a rotation about the z-axis like:
self.triangle.transform = CATransform3DMakeRotation(yaw, 0, 0, 1);
In this case the first argument is the angle (in radians) and the last three arguments specify the axis of rotation.
Note that you should not assign or depend on the value of the frame property of a layer whose transform is not the identity (CGAffineTransformIdentity). When you use the transform property you should set the size and position of your layer by assigning the layer's center and bounds properties, and similarly you should read the center and bounds when you want to find out information about the layer's position and size.
I'm building this to run on the Mac, not iOS - which is quit different. I'm almost there with the speedo, but the math of making the needle move up and down the scale as data is input eludes me.
I'm measuring wind speed live, and want to display it as a gauge - speedometer, with the needle moving as the windspeed changes. I have the fundamentals ok. I can also - and will - load the images into holders, but later. For now I want to get it working ...
- (void)drawRect:(NSRect)rect
{
NSRect myRect = NSMakeRect ( 21, 21, 323, 325 ); // set the Graphics class square size to match the guage image
[[NSColor blueColor] set]; // colour it in in blue - just because you can...
NSRectFill ( myRect );
[[NSGraphicsContext currentContext] // set up the graphics context
setImageInterpolation: NSImageInterpolationHigh]; // highres image
//-------------------------------------------
NSSize viewSize = [self bounds].size;
NSSize imageSize = { 320, 322 }; // the actual image rectangle size. You can scale the image here if you like. x and y remember
NSPoint viewCenter;
viewCenter.x = viewSize.width * 0.50; // set the view center, both x & y
viewCenter.y = viewSize.height * 0.50;
NSPoint imageOrigin = viewCenter;
imageOrigin.x -= imageSize.width * 0.50; // set the origin of the first point
imageOrigin.y -= imageSize.height * 0.50;
NSRect destRect;
destRect.origin = imageOrigin; // set the image origin
destRect.size = imageSize; // and size
NSString * file = #"/Users/robert/Documents/XCode Projects/xWeather Graphics/Gauge_mph_320x322.png"; // stuff in the image
NSImage * image = [[NSImage alloc] initWithContentsOfFile:file];
//-------------------------------------------
NSSize view2Size = [self bounds].size;
NSSize image2Size = { 149, 17 }; // the orange needle
NSPoint view2Center;
view2Center.x = view2Size.width * 0.50; // set the view center, both x & y
view2Center.y = view2Size.height * 0.50;
NSPoint image2Origin = view2Center;
//image2Origin.x -= image2Size.width * 0.50; // set the origin of the first point
image2Origin.x = 47;
image2Origin.y -= image2Size.height * 0.50;
NSRect dest2Rect;
dest2Rect.origin = image2Origin; // set the image origin
dest2Rect.size = image2Size; // and size now is needle size
NSString * file2 = #"/Users/robert/Documents/XCode Projects/xWeather Graphics/orange-needle01.png";
NSImage * image2 = [[NSImage alloc] initWithContentsOfFile:file2];
// do image 1
[image setFlipped:YES]; // flip it because everything else is in this exerecise
// do image 2
[image2 setFlipped:YES]; // flip it because everything else is in this exerecise
[image drawInRect: destRect
fromRect: NSZeroRect
operation: NSCompositeSourceOver
fraction: 1.0];
[image2 drawInRect: dest2Rect
fromRect: NSZeroRect
operation: NSCompositeSourceOver
fraction: 1.0];
NSBezierPath * path = [NSBezierPath bezierPathWithRect:destRect]; // draw a red border around the whole thing
[path setLineWidth:3];
[[NSColor redColor] set];
[path stroke];
}
// flip the ocords
- (BOOL) isFlipped { return YES; }
#end
The result is here. The gauge part that is. Now all I have to do is make the needle move in response to input.
Apple has some sample code, called SpeedometerView, which does exactly what you're asking. It'll surely take some doing to adapt it for your use, but it's probably a decent starting point.
Can I change the color of NSTableView's border. The gray line at the pointer.
Thanks.
You need to SubClass your NSScrollView . NSScrollView doesn't usually do any drawing, and probably has a weird interaction with its child views in that way. I'd suggest putting something like
- (void)drawRect:(NSRect)dirtyRect {
[super drawRect:dirtyRect];
// We're going to be modifying the state for this,
// so allow it to be restored later
[NSGraphicsContext saveGraphicsState];
// Choose the correct color; isFirstResponder is a custom
// ivar set in becomeFirstResponder and resignFirstResponder
[[NSColor redColor]set];
// Create two rects, one slightly outset from the bounds,
// one slightly inset
NSRect bounds = [self bounds];
NSRect innerRect = NSInsetRect(bounds, 2, 2);
NSRect outerRect = NSMakeRect(bounds.origin.x - 2,
bounds.origin.y - 2,
bounds.size.width + 4,
bounds.size.height + 4);
// Create a bezier path using those two rects; this will
// become the clipping path of the context
NSBezierPath * clipPath = [NSBezierPath bezierPathWithRect:outerRect];
[clipPath appendBezierPath:[NSBezierPath bezierPathWithRect:innerRect]];
// Change the current clipping path of the context to
// the enclosed area of clipPath; "enclosed" defined by
// winding rule. Drawing will be restricted to this area.
// N.B. that the winding rule makes the order that the
// rects were added to the path important.
[clipPath setWindingRule:NSEvenOddWindingRule];
[clipPath setClip];
// Fill the rect; drawing is clipped and the inner rect
// is not drawn in
[[NSBezierPath bezierPathWithRect:outerRect] fill];
[NSGraphicsContext restoreGraphicsState];
}
Easy way to set border to scrollview
let scrollView = NSScrollView()
scrollView.contentView.wantsLayer = true
scrollView.contentView.layer?.borderColor = NSColor.black
scrollView.contentView.layer?.cornerRadius = 6
I have a UIImageView with an image in it. I have rotated the image prior to display by setting the transform property of the UIImageView to CGAffineTransformMakeRotation(angle) where angle is the angle in radians.
I want to be able to create another UIImage that corresponds to the rotated version that I can see in my view.
I am almost there, by rotating the image context I get a rotated image:
- (UIImage *) rotatedImageFromImageView: (UIImageView *) imageView
{
UIImage *rotatedImage;
// Get image width, height of the bounding rectangle
CGRect boundingRect = [self getBoundingRectAfterRotation: imageView.bounds byAngle:angle];
// Create a graphics context the size of the bounding rectangle
UIGraphicsBeginImageContext(boundingRect.size);
CGContextRef context = UIGraphicsGetCurrentContext();
// Rotate and translate the context
CGAffineTransform ourTransform = CGAffineTransformIdentity;
ourTransform = CGAffineTransformConcat(ourTransform, CGAffineTransformMakeRotation(angle));
CGContextConcatCTM(context, ourTransform);
// Draw the image into the context
CGContextDrawImage(context, CGRectMake(0, 0, imageView.image.size.width, imageView.image.size.height), imageView.image.CGImage);
// Get an image from the context
rotatedImage = [UIImage imageWithCGImage: CGBitmapContextCreateImage(context)];
// Clean up
UIGraphicsEndImageContext();
return rotatedImage;
}
However the image is not rotated about its centre. I have tried all kinds of transforms concatenated with my rotate to get it to rotate around the centre but to no avail. Am I missing a trick? Is this even possible since I am rotating the context not the image?
Getting desperate to make this work now, so any help would be appreciated.
Dave
EDIT: I've been asked several times for my boundingRect code, so here it is:
- (CGRect) getBoundingRectAfterRotation: (CGRect) rectangle byAngle: (CGFloat) angleOfRotation {
// Calculate the width and height of the bounding rectangle using basic trig
CGFloat newWidth = rectangle.size.width * fabs(cosf(angleOfRotation)) + rectangle.size.height * fabs(sinf(angleOfRotation));
CGFloat newHeight = rectangle.size.height * fabs(cosf(angleOfRotation)) + rectangle.size.width * fabs(sinf(angleOfRotation));
// Calculate the position of the origin
CGFloat newX = rectangle.origin.x + ((rectangle.size.width - newWidth) / 2);
CGFloat newY = rectangle.origin.y + ((rectangle.size.height - newHeight) / 2);
// Return the rectangle
return CGRectMake(newX, newY, newWidth, newHeight);
}
OK - at last I seem to have done it. Any comments on the correctness would be useful... needed a translate, a rotate, a scale and an offset from the drawing rect position to make it work. Code is here:
CGAffineTransform transform = CGAffineTransformIdentity;
transform = CGAffineTransformTranslate(transform, boundingRect.size.width/2, boundingRect.size.height/2);
transform = CGAffineTransformRotate(transform, angle);
transform = CGAffineTransformScale(transform, 1.0, -1.0);
CGContextConcatCTM(context, transform);
// Draw the image into the context
CGContextDrawImage(context, CGRectMake(-imageView.image.size.width/2, -imageView.image.size.height/2, imageView.image.size.width, imageView.image.size.height), imageView.image.CGImage);
// Get an image from the context
rotatedImage = [UIImage imageWithCGImage: CGBitmapContextCreateImage(context)];
I've got an NSView (myView) wrapped in an NSScrollView (myScrollView). Using zoom-in/out buttons, the user can alter the scale of myView. If the user is currently scrolled to a particular spot in myView, I'd like to keep that part of the view on-screen after the zooming has taken place.
I've got code that looks like this:
// preserve current position in scrollview
NSRect oldVisibleRect = [[myScrollView contentView] documentVisibleRect];
NSPoint oldCenter = NSPointFromCGPoint(CGPointMake(oldVisibleRect.origin.x + (oldVisibleRect.size.width / 2.0),
oldVisibleRect.origin.y + (oldVisibleRect.size.height / 2.0)));
// adjust my zoom
++displayZoom;
[self scaleUnitSquareToSize:NSSizeFromCGSize(CGSizeMake(0.5, 0.5))];
[self calculateBounds]; // make sure my frame & bounds are at least as big as the visible content view
[self display];
// Adjust scroll view to keep the same position.
NSRect newVisibleRect = [[myScrollView contentView] documentVisibleRect];
NSPoint newOffset = NSPointFromCGPoint(CGPointMake((oldCenter.x * 0.5) - (newVisibleRect.size.width / 2.0),
(oldCenter.y * 0.5) - (newVisibleRect.size.height / 2.0)));
if (newOffset.x < 0)
newOffset.x = 0;
if (newOffset.y < 0)
newOffset.y = 0;
[[myScrollView contentView] scrollToPoint: newOffset];
[myScrollView reflectScrolledClipView: [myScrollView contentView]];
And it seems sort of close, but it's not quite right and I can't figure out what I'm doing wrong. My two questions are:
1) Is there not a built-in something along the lines of:
[myView adjustScaleBy: 0.5 whilePreservingLocationInScrollview:myScrollView];
2) If not, can anyone see what I'm doing wrong in my "long way around" approach, above?
Thanks!
Keeping the same scroll position after scaling isn't easy. One thing you need to decide is what you mean by "the same" - do you want the top, middle, or bottom of the visible area before scaling to stay in place after scaling?
Or, more intuitively, do you want the position that stays in place a percentage down the visible rect equal to the percentage that you are scrolled down the document when you start (eg, so the center of the scroller's thumb doesn't move up or down during a scale, the thumb just grows or shrinks).
If you want the latter effect, one way to do it is get the NSScrollView's verticalScroller and horizontalScroller, and then read their 'floatValue's. These are normalized from 0 to 1, where '0' means you're at the top of the document and 1 means you're at the end. The nice thing about asking the scroller for this is that if the document is shorter than the NSScrollView, the scroller still returns a sane answer in all cases for 'floatValue,' so you don't have to special-case this.
After you resize, set the NSScrollView's scroll position to be the same percentage it was before the scale - but, sadly, here's where I wave my hands a little bit. I haven't done this in a while in my code, but as I recall you can't just set the NSScrollers' 'floatValue's directly - they'll LOOK scrolled, but they won't actually affect the NSScrollView.
So, you'll have to write some math to calculate the new top-left point in your document based on the percentage you want to be through it - on the y axis, for instance, it'll look like, "If the document is now shorter than the scrollView's contentView, scroll to point 0, otherwise scroll to a point that's ((height of contentView - height of documentView) * oldVerticalPercentage) down the document." X axis is of course similar.
Also, I'm almost positive you don't need a call to -display here, and in general shouldn't ever call it, ever. (-setNeedsDisplay: at most.)
-Wil
Me thinks you like to type too much… ;-)
// instead of this:
NSPoint oldCenter = NSPointFromCGPoint(CGPointMake(oldVisibleRect.origin.x +
(oldVisibleRect.size.width / 2.0),
// use this:
NSPoint oldCenter = NSMakePoint(NSMidX(oldVisibleRect), NSMaxY(oldVisibleRect));
// likewise instead of this:
[self scaleUnitSquareToSize:NSSizeFromCGSize(CGSizeMake(0.5, 0.5))];
// use this:
[self scaleUnitSquareToSize:NSMakeSize(0.5, 0.5)];
// and instead of this
NSPoint newOffset = NSPointFromCGPoint(CGPointMake(
(oldCenter.x * 0.5) - (newVisibleRect.size.width / 2.0),
(oldCenter.y * 0.5) - (newVisibleRect.size.height / 2.0)));
// use this:
NSPoint newOffset NSMakePoint(
(oldCenter.x - NSWidth(newVisibleRect)) / 2.f,
(oldCenter.y - NSHeight(newVisibleRect)) / 2.f);
This is an old question, but I hope someone looking for this finds my answer useful...
float zoomFactor = 1.3;
-(void)zoomIn
{
NSRect visible = [scrollView documentVisibleRect];
NSRect newrect = NSInsetRect(visible, NSWidth(visible)*(1 - 1/zoomFactor)/2.0, NSHeight(visible)*(1 - 1/zoomFactor)/2.0);
NSRect frame = [scrollView.documentView frame];
[scrollView.documentView scaleUnitSquareToSize:NSMakeSize(zoomFactor, zoomFactor)];
[scrollView.documentView setFrame:NSMakeRect(0, 0, frame.size.width * zoomFactor, frame.size.height * zoomFactor)];
[[scrollView documentView] scrollPoint:newrect.origin];
}
-(void)zoomOut
{
NSRect visible = [scrollView documentVisibleRect];
NSRect newrect = NSOffsetRect(visible, -NSWidth(visible)*(zoomFactor - 1)/2.0, -NSHeight(visible)*(zoomFactor - 1)/2.0);
NSRect frame = [scrollView.documentView frame];
[scrollView.documentView scaleUnitSquareToSize:NSMakeSize(1/zoomFactor, 1/zoomFactor)];
[scrollView.documentView setFrame:NSMakeRect(0, 0, frame.size.width / zoomFactor, frame.size.height / zoomFactor)];
[[scrollView documentView] scrollPoint:newrect.origin];
}