Getting co-ordinates of bitmap on mouseDown in NSImageView - objective-c

I am working on an app where I display unsupported (by XCode) file format). So I've subclassed NSBitmapImageRep to display it in a subclass of NSImaageView. I've set it up to be proportionally scallable (up or down). Now I need to add a possibility to get coordinates of pixel in a bitmap. So I've ovveride mouseDown: method:
- (void)mouseDown:(NSEvent *)theEvent
{
NSLog(#"mouseDown: %ld", [theEvent clickCount]);
NSPoint point = [theEvent locationInWindow];
NSLog(#"point x: %f and y: %f", point.x, point.y);
}
After getting NSPoint I should try to convert it to co-ordinates of a bitmap BUT first I have no idea How to solve the problem that locationInWindow returns NSPoint of a NSImageView, not of the bitmap which is ussually smaller and has unused margins in NSImageView, but I can click on the margin and mouseDown event returns me NSPoint in that margin. Do you have any idea what I shoud do?

You need a view to image matrix. It's a matrix that maps view coordinates to image coordinates. Usually it will be a combination of scale and translation.
First you need to decide if you want to scale the image so that it is entirely visible, but fits within the window, or so that it entirely fills the window. (Or there are other options, like always showing the image at 1:1 regardless of the size of the window.) That will determine the scale of the image.
Next you need to decide how to position the scaled image. If you scale it to always fit in the window, and there's padding, do you favor the left and top of the window, or always try to center it? IF it's scaled to always fill the window, is it centered vertically or horizontally in the window? Or is the origin of the image always displayed in the lower left of the window?
Once you've figured out the scale and translate, you can compose a single matrix from the 2 of them. Once you've done that, get the inverse of the matrix, and that will transform your view coordinates into image pixels.

Related

Cocoa - NSTextField with only top-left and bottom-left rounded corners

I'm trying to make an NSTextField with only two rounded corners on the top-left and bottom-left. I tried to do the following, but in this case I get all corners rounded:
[self.myTextField:YES];
self.myTextField.layer.cornerRadius = 5;
What should I do to have only two (or for example one) rounded corners?
I'm a little out of my lane in OS X, but what you want to do is embed whatever corner art you like into an image and present those on an image view behind the NSTextField.
Resize the image view exactly as you do the text field, but first set the image's capInsets property. Size the insets to exclude the corners from scaling as the extent of the image changes. (Make sure your corner art is placed at the extreme edges of the image).

How to manage coordinates in a NSView

I'm new in Mac programming. The first thing I've noticed is that the (0, 0) coordinate in a NSView is the bottom-left corner.
I'm going to use a NSView as a canvas, to draw objects on it. My objects will be positioned from the top-left corner. I can convert Y coordinates using a geometrical transformation.
But the problem is: I'm not sure how to proceed when the view is resized. I don't want to calculate the layout every resize event, because calculating it takes between 150 and 250 ms.
So, to Mac experts:
Do you know a good practice calculating the layout based on the top-left corner?
How should I manage the resize behavior?
Do you know other techniques, flipping the graphics object, or something like that, to draw this kind of stuff?
Just implement
- (void)isFlipped {
return YES;
}
In your NSView subclass.

CATextLayer subpixel antialiasing

My app draws layer-backed labels over a NSImageView.
The image view displays an image, and a tint color over that image.
This ensures that the contrast between the labels and the background image works.
As you can see, subpixel antialiasing is enabled and works correctly.
When you hover over those labels, they animate the frame property (Actually the view containing them).
While animating, the subpixel antialiasing is disabled, and when done enabled again.
This looks incredibly weird.
The layer is never redrawn, and the subpixel antialiasing doesn't have to change.
So I don't see a good reason why it shouldn't be displayed when animating.
I've tried everything I can think of.
Making the NSTextField opaque
Making the CATextLayer opaque
Giving the NSTextField a background-color
Giving the CATextLayer a background-color
Always the same result.
Disabling subpixel antialiasing for the labels is not an option, since it's not well readable on non-retina devices.
EDIT
I forgot that the layer is replaced with a presentationLayer while animating.
This layer probably does not support subpixel antialiasing, which is why it's disabled.
Now the question is if I can replace this presentationLayer with a CATextLayer.
What I also noticed is that setting shouldRasterize to YES enabled subpixel antialiasing also for animation, but only against the background color. So no background-color will bring no subpixel antialiasing.
Is there any way that you can post a piece of sample code? I quickly mocked up an NSWindow, added an NSImageView, added a background-less NSTextField with setWantsLayer: set to YES. In my applicationDidFinishLaunching: I set a new rect on the NSTextField's Animator frame, but I didn't see any pixelation.
The problem is with positioning of the text layer. Let's presume you use left alignment. The text will look good if x and y coordinates of the layer's frame origin are rounded numbers. For example:
CGFloat x = 10.6;
CGFloat y = 10.3;
textLayer.frame = CGRectMake(x, y, width, height); // the text will be blur.
textLayer.frame = CGRectMake(round(x), round(y), width, height); // the text will not be blur.
So, the first problem may be that the coordinates you assign to the layer's frame are not rounded.
The tricky part is that the edge of the layer may still be not aligned with pixels even if you think you passed rounded coordinates. This may happen if the anchorPoint of your layer is (0.5, 0.5), which is the default value. If you set:
textLayer.position = CGPointMake(10.0, 10.0);
you may think it should draw the text sharp. However, position point is in the center of the layer here, and depending on the layer's width and height the left and top edge's coordinates may be fractional numbers.
If you want to make a quick test do this.
Use textLayer.frame = frame instead of using position and anchor point, it will assign the coordinates directly to the frame.
Make sure the numbers you use in the frame are rounded.
Do not mess with rendering mechanism, remove the code that changes shouldRasterize, antialiasing, etc.
If this makes the text sharp, you can start using the anchor point and position and to see how the result changes.

drawInRect behavior with UIView rotation transform

I have a problem understanding how the parameter passed to the drawInRect method is defined when a rotation transformation is performed on a UIView.
To give an example I have a UIView which I rotated with an angle of 307 degree.
In the drawInRect method I log the following:
self.frame: {{103.932, 273.254}, {64.3007, 84.3374}}
rect (the variable passed as parameter:{{0, 0.102913}, {18, 89}}
The problem is that according to the documentation I should not draw outside of rect, but considering what I should draw, there is no way my images will fit there.
Can anyone explain to me how I am supposed to use drawInRect in the case my UIView is rotated ?
To give more detail about my problem, here is what I do:
I have a scrollview with a contentView inside (subclassed). I add my UIViews in the content view.
The views in question are composed of a handler image (bottom left) and the main image (top right). Users are supposed to grab the view by pressing the handler but that's not the point.
The drawInRect method of the UIView contains the following:
[_image drawInRect:CGRectMake(handlerSize.width, 0, _image.size.width, _image.size.height)];
CGSize size = CGSizeMake(kHandPickerWidth/self.scrollViewScale, kHandPickerHeight/self.scrollViewScale);
[_handPickerImage drawInRect:(0, _image.size.height, size.width, size.height)];
The UIViews objects are added at viewWillAppear in the content view doing the following:
first instanciate,
then addSubview:
then I set the scrollViewScale parameter,
then I set the frame parameter (according to the top right image displayed (which may vary)
then I rotate the UIView.
Starting from line three, the code is executed from the
- (void)scrollViewDidZoom:(UIScrollView *)scrollView
to make sure every variable is set properly when displayed is needed.
The line defining the size variable is to adjust the marker's size no mater the zoomScale value of the scroll view.
You basically just draw as you would normally, and the painting will be rotated by iOS for you. You can get this transformation information if you would want to.
You need to get a reference to the currect graphics context:
CGContextRef ctx = UIGraphicsGetCurrentContext();
Then query the transformation matrix directly:
CGAffineTransform tf = CGContextGetCTM(ctx);
NSLog(#"current ctm: %#",NSStringFromCGAffineTransform(tf));
or better, get the transfrom from your drawing function to the device:
CGAffineTransform tf = CGContextGetUserSpaceToDeviceSpaceTransform(ctx);
NSLog(#"user->device transform: %#",NSStringFromCGAffineTransform(tf));
And in drawRect: you should not rely too much on the passed CGRect, because it serves mostly as a hint to what piece of the view needs updating. (e.g. because you called -setNeedsDisplayInRect: on it). To get the actual bounds where your view lives in, use self.bounds.
Drawing outside of the CGRect is no real problem, but will only hurt performance a little.
edit
ps. to clarify: self.frame is the frame of your view in the parent view coordinate system. It changes if you move, rotate or otherwise transform the view. self.bounds is the frame of your view in its own coordinate system, and (therefore) remains constant under changes of position or transformations.
So I found a solution to my problem:
I was setting the frame parameter multiple times with some CGAffineTransformation defined which is not supposed to be done.
Now each time I need to reset the frame I reset the affine transform, change the frame and set the back the affine transform.
Everything works as supposed to this way.

Difficulty finding correct anchor point for rotation transforms

I'm animating a button's position, rotating it around a circle. This creates a UISwitch-like behavior, except it is a lot more fancy. The button rotates, but it ends up off the desired position by about 0.25 radians. I'm trying to figure out where to put the anchor point to make the button rotate in a perfect circle around its origin.
Here's the code that I use to make the button "orbit" with a 120 pixel radius from the original location.
float offsetX=120;
float offsetY=0;
enableDisableButton.layer.anchorPoint =
CGPointMake(offsetX/enableDisableButton.frame.size.width,
offsetY/enableDisableButton.frame.size.height);
I use the following method to do the calculations. Passing an argument of 90 for degrees, I expect to see the button start at a 180˚ position and move to 90˚, still 120 pixels away from its origin
-(CGAffineTransform)calculateLabelPositionFromFixedPointWithOffsetClockwiseInDegrees:(float)degrees
{
float rotation = degrees*(2*M_PI/360);
CGAffineTransform transform24 = CGAffineTransformMakeRotation(rotation);
NSLog(#"rotation: %f", rotation);
return transform24;
}
This little 0.25 radian or so offset means that I need to visually confirm the location of each button and cannot easily adjust its location programmatically. Any help in determining the correct way to rotate the button is appreciated.
I tried other ways to rotate objects. For example, I have an arrow
<--------x, and I would like to rotate this arrow in a circle around x . What is the correct anchor point placement for this operation? Is it [1, 0.5]?
An easier way to do this kind of rotations is to put an object within a symmetric UIView, center it at the desired point of rotation and assign a rotation transform. This works, but requires the view to be twice as big:
Y----x----Y < this rotates Y around center point X without any anchor point adjustments. this is a very useful method to rotate arrows within analog gauges and such.
"An easier way to do this kind of rotations is to put an object within a symmetric UIView, center it at the desired point of rotation and assign a rotation transform." this way is fine.
The anchor point of your enableDisableButton in the example would be
CGPointMake(offsetX/enableDisableButton.frame.size.width, 0)
i.e. not vertically centered in your button. Rotating around the anchor point would result in the button being offset to the top or bottom of the desired position. The transform looks alright, so I think it is just the anchor point. It should be:
CGPointMake(offsetX/enableDisableButton.frame.size.width, 0.5f)