reset image in tableView imageCell is blurry - cocoa

i'm trying to show an 'info' icon during a cursor 'rollover' on an NSTableView cell. i'm getting a copy of the cell's image, drawing the 'info' icon on it, and telling the cell to setImage with this copy. just drawing the icon will scale it and it won't be the same size in every one as the images in the table are different sizes. i'm not having a problem with the scaling or positioning the correct size icon.
my problem is that the replaced image is slightly blurry and it's edges are not crisp on close examination. the lack of edge makes it appear to be 'moving' slightly when the mouseEntered happens and the image is replaced.
i've tried a number of drawing techniques that doen't use lockFocus on an NSImage (drawing in CGBitmapContext, or using CIFilter compositing), and they produce the same results.
i'm using NSTableView's preparedCellAtColumn as it seems that drawing in willDisplayCell is unpredictable -- i read somewhere but can't remember where.
here is my preparedCellAtColumn method:
- (NSCell *)preparedCellAtColumn:(NSInteger)column row:(NSInteger)row
{
NSCell *cell = [super preparedCellAtColumn:column row:row];
if ((self.mouseOverRow == row) && column == 0) {
NSCell * imageCell = [super preparedCellAtColumn:0 row:row];
NSImage *sourceImage = [[imageCell image] copy];
NSRect cellRect = [self frameOfCellAtColumn:0 row:row];
NSSize cellSize = cellRect.size;
NSSize scaledSize = [sourceImage proportionalSizeForTargetSize:cellSize];
NSImage *outputImage = [[NSImage alloc] initWithSize:cellSize];
[outputImage setBackgroundColor:[NSColor clearColor]];
NSPoint drawPoint = NSZeroPoint;
drawPoint.x = (cellSize.width - scaledSize.width) * 0.5;
drawPoint.y = (cellSize.height - scaledSize.height) * 0.5;
NSRect drawRect = NSMakeRect(drawPoint.x, drawPoint.y, scaledSize.width, scaledSize.height);
NSPoint infoPoint = drawPoint;
infoPoint.x += NSWidth(drawRect) - self.infoSize.width;
[outputImage lockFocus];
[sourceImage drawInRect:drawRect fromRect:NSZeroRect operation:NSCompositeSourceOver fraction:1.0];
[self.infoImage drawAtPoint:infoPoint fromRect:NSZeroRect operation:NSCompositeSourceOver fraction:1.0];
[outputImage unlockFocus];
[cell setImage:outputImage];
}
return cell;
}
[this is the enclosed scaling method from scott stevenson]
- (NSSize)proportionalSizeForTargetSize:(NSSize)targetSize
{
NSSize imageSize = [self size];
CGFloat width = imageSize.width;
CGFloat height = imageSize.height;
CGFloat targetWidth = targetSize.width;
CGFloat targetHeight = targetSize.height;
CGFloat scaleFactor = 0.0;
CGFloat scaledWidth = targetWidth;
CGFloat scaledHeight = targetHeight;
if ( NSEqualSizes( imageSize, targetSize ) == NO )
{
CGFloat widthFactor;
CGFloat heightFactor;
widthFactor = targetWidth / width;
heightFactor = targetHeight / height;
if ( widthFactor < heightFactor )
scaleFactor = widthFactor;
else
scaleFactor = heightFactor;
scaledWidth = width * scaleFactor;
scaledHeight = height * scaleFactor;
}
return NSMakeSize(scaledWidth,scaledHeight);
}
i need to support 10.6, so can't use new groovy lion methods.
thanks for your consideration...

Your code is setting the origin of the image to non-integral coordinates. That means that the edge of the image will not be pixel-aligned, producing a fuzzy result.
You just need to do this:
NSPoint drawPoint = NSZeroPoint;
drawPoint.x = round((cellSize.width - scaledSize.width) * 0.5);
drawPoint.y = round((cellSize.height - scaledSize.height) * 0.5);
That will ensure the image origin has no fractional component.

Related

Rotate an NSImage by arbitrary angle and supporting Retina displays

I need to rotate an NSImage by an arbitrary angle. For the iOS version of my app, I hacked together the following solution from several sources I found on the internets:
#implementation UIImage (ut_rotate)
- (instancetype)imageRotatedByDegrees:(CGFloat)degrees{
CGFloat scale = [UIScreen mainScreen].scale; // support retina displays
float newSide = self.size.width; // square img
CGSize size = CGSizeMake(newSide, newSide);
UIGraphicsBeginImageContextWithOptions(size, NO, 0.0);
CGContextRef context = UIGraphicsGetCurrentContext();
CGAffineTransform transform = CGAffineTransformIdentity;
transform = CGAffineTransformTranslate(transform, newSide/2.0f, newSide/2.0f);
transform = CGAffineTransformRotate(transform, _heading * M_PI/180.0);
transform = CGAffineTransformScale(transform, 1.0, -1.0);
CGContextConcatCTM(context, transform);
CGContextDrawImage(context, CGRectMake(-newSide/2.0f, -newSide/2.0f, newSide, newSide), _image.CGImage);
return [UIImage imageWithCGImage: CGBitmapContextCreateImage(context) scale:scale orientation:UIImageOrientationUp];
}
#end
Note that this hack supports retina display.
For Cocoa, I found the canonical solution on one of the internets:
#implementation NSImage (ut_rotate)
- (NSImage*)imageRotatedByDegrees:(CGFloat)degrees {
// Calculate the bounds for the rotated image
// We do this by affine-transforming the bounds rectangle
NSRect imageBounds = {NSZeroPoint, [self size]};
NSBezierPath* boundsPath = [NSBezierPath bezierPathWithRect:imageBounds];
NSAffineTransform* transform = [NSAffineTransform transform];
[transform rotateByDegrees:-1.0 * degrees];// we want clockwise angles
[boundsPath transformUsingAffineTransform:transform];
NSRect rotatedBounds = {NSZeroPoint, [boundsPath bounds].size};
NSImage* rotatedImage = [[NSImage alloc] initWithSize:rotatedBounds.size] ;
// Center the image within the rotated bounds
imageBounds.origin.x = NSMidX(rotatedBounds) - (NSWidth(imageBounds) / 2);
imageBounds.origin.y = NSMidY(rotatedBounds) - (NSHeight(imageBounds) / 2);
// Start a new transform, to transform the image
transform = [NSAffineTransform transform];
// Move coordinate system to the center
// (since we want to rotate around the center)
[transform translateXBy:+(NSWidth(rotatedBounds) / 2)
yBy:+(NSHeight(rotatedBounds) / 2)];
// Do the rotation
[transform rotateByDegrees:-1.0 * degrees];
// Move coordinate system back to normal (bottom, left)
[transform translateXBy:-(NSWidth(rotatedBounds) / 2)
yBy:-(NSHeight(rotatedBounds) / 2)];
// Draw the original image, rotated, into the new image
// Note: This "drawing" is done off-screen.
[rotatedImage lockFocus];
[transform concat];
[self drawInRect:imageBounds fromRect:NSZeroRect operation:NSCompositeCopy fraction:1.0] ;
[rotatedImage unlockFocus];
return rotatedImage;
}
#end
Problem is: a) results look blurry already on non-retina displays b) doesn't support retina.
I found something about substituting code between lockFocus and unlockFocus with block-based drawing methods but don't understand it.
I'm looking for a solution that produces sharper images and supports retina. For my needs only square source images have to be supported on OS X 10.9 and later.
This is completely untested but may work:
#implementation NSImage (ut_rotate)
- (NSImage*)imageRotatedByDegrees:(CGFloat)degrees {
// Calculate the bounds for the rotated image
// We do this by affine-transforming the bounds rectangle
NSRect imageBounds = {NSZeroPoint, [self size]};
NSBezierPath* boundsPath = [NSBezierPath bezierPathWithRect:imageBounds];
NSAffineTransform* transform = [NSAffineTransform transform];
[transform rotateByDegrees:-1.0 * degrees];// we want clockwise angles
[boundsPath transformUsingAffineTransform:transform];
NSRect rotatedBounds = {NSZeroPoint, [boundsPath bounds].size};
// Center the image within the rotated bounds
imageBounds.origin.x = NSMidX(rotatedBounds) - (NSWidth(imageBounds) / 2);
imageBounds.origin.y = NSMidY(rotatedBounds) - (NSHeight(imageBounds) / 2);
NSImage* rotatedImage = [NSImage imageWithSize:rotatedBounds.size flipped:NO drawingHandler:^BOOL (NSRect dstRect){
// Start a new transform, to transform the image
NSAffineTransform* transform = [NSAffineTransform transform];
// Move coordinate system to the center
// (since we want to rotate around the center)
[transform translateXBy:+(NSWidth(rotatedBounds) / 2)
yBy:+(NSHeight(rotatedBounds) / 2)];
// Do the rotation
[transform rotateByDegrees:-1.0 * degrees];
// Move coordinate system back to normal (bottom, left)
[transform translateXBy:-(NSWidth(rotatedBounds) / 2)
yBy:-(NSHeight(rotatedBounds) / 2)];
// Draw the original image, rotated, into the new image
[transform concat];
[self drawInRect:imageBounds fromRect:NSZeroRect operation:NSCompositeCopy fraction:1.0];
return YES;
}];
return rotatedImage;
}
#end
My extension for NSImage, work properly with all degrees
- (NSImage *)rotateByDegrees:(CGFloat)degrees
{
NSImage *image = nil;
if (degrees == 0.0)
{
image = self;
}
else
{
CIImage *ciImage = [[[CIImage alloc] initWithData:self.TIFFRepresentation] autorelease];
CGFloat angle = degrees * M_PI / 180.0;
CIImage *output = [ciImage imageByApplyingTransform:CGAffineTransformMakeRotation(angle)];
image = [[[NSImage alloc] initWithSize:NSMakeSize(self.size.width * fabs(cos(angle)) + self.size.height * fabs(sin(angle)), self.size.width * fabs(sin(angle)) + self.size.height * fabs(cos(angle)))] autorelease];
[image addRepresentation:[NSCIImageRep imageRepWithCIImage:output]];
}
return image;
}

Core Animation -- Nothing Appears (OS X)

I am attempting to draw this plane with Core Animation in OS X:
(without the black background, however).
This is the code in my custom NSView:
- (id)initWithFrame:(NSRect)frame
{
self = [super initWithFrame:frame];
if (self) {
[self A_singlePlane];
}
return self;
}
- (void)A_singlePlane{
CALayer *container = [CALayer layer];
container.frame = CGRectMake(0, 0, self.frame.size.width, self.frame.size.height);
[self.layer addSublayer:container];
CALayer *purpleLayer = [self addPlaneToLayer:container size:CGSizeMake(100, 100) position:CGPointMake(100, 100) color:[NSColor purpleColor]];
CATransform3D t = CATransform3DIdentity;
t = CATransform3DRotate(t, 45.0f * M_PI / 180.f, 0, 1, 0);
purpleLayer.transform = t;
}
- (CALayer *)addPlaneToLayer:(CALayer* )container size:(CGSize)size position:(CGPoint)point color:(NSColor *)color{
CALayer *plane = [CALayer layer];
plane.backgroundColor = [color CGColor];
plane.opacity = 0.6;
plane.frame = CGRectMake(point.x, point.y, size.width, size.height);
plane.borderColor = [[NSColor colorWithWhite:1.0 alpha:0.5] CGColor];
plane.borderWidth = 3.0;
plane.cornerRadius = 10.0;
[container addSublayer:plane];
return plane;
}
I have a view in interface builder with the class set as my custom NSView class. My custom view is set to have a layer. The result is a blank NSWindow. I do not receive any errors or warnings.
Does anyone see where my logical flaw is?

Image size resets to original after performing pich using UIPichGestureRecognizer in iphone

I have one view controller in which I have added UIScrollView & UIImageView programatically. I have added UIPichGestureRecognizer to the UIImageView. My UIImageView is added to UIScrollView as a subview.
My problem is when I try to pinch the image , it zoom in. But when I release the touches from screen it again come to its default size. I can not find the error in code. Please help me.
Below is my code
- (void)createUserInterface {
scrollViewForImage = [[UIScrollView alloc]initWithFrame:CGRectMake(20.0f, 60.0f, 280.0f, 200.0f)];
scrollViewForImage.userInteractionEnabled = YES;
scrollViewForImage.multipleTouchEnabled = YES;
scrollViewForImage.backgroundColor = [UIColor redColor];
scrollViewForImage.autoresizesSubviews = YES;
scrollViewForImage.maximumZoomScale = 1;
scrollViewForImage.minimumZoomScale = .50;
scrollViewForImage.clipsToBounds = YES;
scrollViewForImage.delegate = self;
scrollViewForImage.bouncesZoom = YES;
scrollViewForImage.contentMode = UIViewContentModeScaleToFill;
[self.contentView addSubview:scrollViewForImage];
imageView = [[UIImageView alloc]initWithFrame:CGRectMake(0.0f, 0.0f, 280.0f, 200.0f)];
[imageView setBackgroundColor:[UIColor clearColor]];
imageView.userInteractionEnabled = YES;
imageView.multipleTouchEnabled = YES;
UIPinchGestureRecognizer *pinchRecognizer = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:#selector(pinch:)];
[pinchRecognizer setDelegate:self];
[imageView addGestureRecognizer:pinchRecognizer];
//[self.contentView addSubview:imageView];
[self.scrollViewForImage addSubview:imageView];
scrollViewForImage.contentSize = CGSizeMake(imageView.frame.size.width , imageView.frame.size.height);
}
-(UIView *) viewForZoomingInScrollView:(UIScrollView *)inScroll {
return imageView;
}
-(void)pinch:(id)sender {
[self.view bringSubviewToFront:[(UIPinchGestureRecognizer*)sender view]];
if([(UIPinchGestureRecognizer*)sender state] == UIGestureRecognizerStateEnded) {
lastScale = 1.0;
return;
}
CGFloat scale = 1.0 - (lastScale - [(UIPinchGestureRecognizer*)sender scale]);
CGAffineTransform currentTransform = [(UIPinchGestureRecognizer*)sender view].transform;
CGAffineTransform newTransform = CGAffineTransformScale(currentTransform, scale, scale);
[[(UIPinchGestureRecognizer*)sender view] setTransform:newTransform];
lastScale = [(UIPinchGestureRecognizer*)sender scale];
}
From what I can see the problem lies here:
scrollViewForImage.maximumZoomScale = 1;
You are setting the maximum zoom scale of the image to 1 x its full size. This means once you finish pinching the image, it will scale back to 1 x its size.
If you want to be able to zoom the image to a larger size, try settings this value to be higher than 1. e.g.
scrollViewForImage.maximumZoomScale = 3;

How do I manage to draw a few images in Custom View and Drag&Drop them

I'm writting a Cocoa app. I've wrote a code that can Drag&Drop one Image. But now I need to draw several Images by double click and D&D them. Each double click- new image, each click on existing image- starts D&D. Problem is in realization. I can't imagine a simple way of realization. Can anybody propose a solution?
Thanks.
#import "DotView.h"
#implementation DotView
- (id)initWithFrame:(NSRect)frame
{
self = [super initWithFrame:frame];
if (self)
{
center.x = center.y = 100.0;
}
return self;
}
- (void)drawRect:(NSRect)rect {
NSRect bounds = [self bounds];
[[NSColor whiteColor] set];
NSRectFill(bounds);
[[NSGraphicsContext currentContext]
setImageInterpolation: NSImageInterpolationHigh];
NSSize viewSize = [self bounds].size;
NSSize imageSize = { 50, 40 };
NSPoint imageOrigin = center;
imageOrigin.x -= imageSize.width * 0.50;
imageOrigin.y -= imageSize.height * 0.50;
NSRect destRect;
destRect.origin = imageOrigin;
destRect.size = imageSize;
NSString * file = #"/Users/classuser/Desktop/ded.jpg";
NSImage * image = [[NSImage alloc] initWithContentsOfFile:file];
[image setFlipped:NO];
[image drawInRect: destRect
fromRect: NSZeroRect
operation: NSCompositeSourceOver
fraction: 1.0];
NSBezierPath * path = [NSBezierPath bezierPathWithRect:destRect];
}
-(void)mouseDown:(NSEvent*)event
{
NSPoint point = [event locationInWindow];
if(center.x<point.x+25 && center.x>point.x-25)
if(center.y<point.y+20 && center.y>point.y-20)
center = [self convertPoint:point fromView:nil];
}
- (void) mouseDragged:(NSEvent*)event {
[self mouseDown:event];
[self setNeedsDisplay:YES];
}
#end
Read:
Cocoa Drawing Guide
and
View Programming Guide for Cocoa
and
Drag and Drop Programming Topics for Cocoa
Once you've read these, ask more targeted questions - this is a bit too broad to answer simply.

How to stop NSScrollView from scrolling to top when horizontally resizing contained NSTextView?

I have a NSTextView that I want to display a horizontal scroll bar. Following some leads on the internet, I have most of it working, except that I am having problems with the vertical scroll bar.
What I have done is to find the width of the longest line (in pixels with the given font) and then I resize the NSTextContainer and the NSTextView appropriately. This way the horizontal scroll bar is representative of the width and scrolling to the right will scroll to the end of the longest line of text.
After doing this work, I noticed that my NSScrollView would show and hide the vertical scroll bar as I typed. I've 'fixed' this problem by setting autohidesScrollers to NO before the resize and then YES afterwards. However, there still remains another problem where, as I type, the vertical scrollbar thumb jumps to the top of the scrollbar and back to the proper place as I type. I type 'a' <space>, it jumps to the top, I press the <space> again and it jumps back to the proper location.
Any thoughts?
Here is some sample code:
- (CGFloat)longestLineOfText
{
CGFloat longestLineOfText = 0.0;
NSRange lineRange;
NSString* theScriptText = [myTextView string];
NSDictionary* attributesDict = [NSDictionary dictionaryWithObject:scriptFont forKey:NSFontAttributeName]; //scriptFont is a instance variable
NSUInteger characterIndex = 0;
NSUInteger stringLength = [theScriptText length];
while (characterIndex < stringLength) {
lineRange = [theScriptText lineRangeForRange:NSMakeRange(characterIndex, 0)];
NSSize lineSize = [[theScriptText substringWithRange:lineRange] sizeWithAttributes:attributesDict];
longestLineOfText = max(longestLineOfText, lineSize.width);
characterIndex = NSMaxRange(lineRange);
}
return longestLineOfText;
}
// ----------------------------------------------------------------------------
- (void)updateMyTextViewWidth
{
static CGFloat previousLongestLineOfText = 0.0;
CGFloat currentLongestLineOfText = [self longestLineOfText];
if (currentLongestLineOfText != previousLongestLineOfText) {
BOOL shouldStopBlinkingScrollBar = (previousLongestLineOfText < currentLongestLineOfText);
previousLongestLineOfText = currentLongestLineOfText;
NSTextContainer* container = [myTextView textContainer];
NSScrollView* scrollView = [myTextView enclosingScrollView];
if (shouldStopBlinkingScrollBar) {
[scrollView setAutohidesScrollers:NO];
}
CGFloat padding = [container lineFragmentPadding];
NSSize size = [container containerSize];
size.width = currentLongestLineOfText + padding * 2;
[container setContainerSize:size];
NSRect frame = [myTextView frame];
frame.size.width = currentLongestLineOfText + padding * 2;
[myTextView setFrame:frame];
if (shouldStopBlinkingScrollBar) {
[scrollView setAutohidesScrollers:YES];
}
}
}
Thanks to Ross Carter's post on the Cocoa-Dev list, I resolved this issue.
A. You have to set up your text view to support horizontal scrolling:
- (void)awakeFromNib {
[myTextView setHorizontallyResizable:YES];
NSSize tcSize = [[myTextView textContainer] containerSize];
tcSize.width = FLT_MAX;
[[myTextView textContainer] setContainerSize:tcSize];
[[myTextView textContainer] setWidthTracksTextView:NO];
}
B. You have to update the width of the text view as it changes, otherwise the horizontal scroll-bar doesn't update properly:
- (void)textDidChange:(NSNotification *)notification
{
[self updateTextViewWidth];
}
- (CGFloat)longestLineOfText
{
CGFloat longestLineOfText = 0.0;
NSLayoutManager* layoutManager = [myTextView layoutManager];
NSRange lineRange;
NSUInteger glyphIndex = 0;
NSUInteger glyphCount = [layoutManager numberOfGlyphs];
while (glyphIndex < glyphCount) {
NSRect lineRect = [layoutManager lineFragmentUsedRectForGlyphAtIndex:glyphIndex
effectiveRange:&lineRange
withoutAdditionalLayout:YES];
longestLineOfText = max(longestLineOfText, lineRect.size.width);
glyphIndex = NSMaxRange(lineRange);
}
return longestLineOfText;
}
// ----------------------------------------------------------------------------
- (void)updateTextViewWidth
{
static CGFloat previousLongestLineOfText = 0.0;
CGFloat currentLongestLineOfText = [self longestLineOfText];
if (currentLongestLineOfText != previousLongestLineOfText) {
previousLongestLineOfText = currentLongestLineOfText;
NSTextContainer* container = [myTextView textContainer];
CGFloat padding = [container lineFragmentPadding];
NSRect frame = [myTextView frame];
frame.size.width = currentLongestLineOfText + padding * 2;
[myTextView setFrame:frame];
}
}

Resources