diff --git a/modules/highgui/src/window_cocoa.mm b/modules/highgui/src/window_cocoa.mm index 73959fd3d3..4e6da5a62b 100644 --- a/modules/highgui/src/window_cocoa.mm +++ b/modules/highgui/src/window_cocoa.mm @@ -74,7 +74,6 @@ CV_IMPL int cvWaitKey (int maxWait) {return 0;} #include -const int TOP_BORDER = 7; const int MIN_SLIDER_WIDTH=200; static NSApplication *application = nil; @@ -82,10 +81,10 @@ static NSAutoreleasePool *pool = nil; static NSMutableDictionary *windows = nil; static bool wasInitialized = false; -@interface CVView : NSView { - NSImage *image; -} +@interface CVView : NSView +@property(retain) NSView *imageView; @property(retain) NSImage *image; +@property int sliderHeight; - (void)setImageData:(CvArr *)arr; @end @@ -221,32 +220,38 @@ CV_IMPL void cvShowImage( const char* name, const CvArr* arr) if(window) { bool empty = [[window contentView] image] == nil; - NSRect rect = [window frame]; NSRect vrectOld = [[window contentView] frame]; - + NSSize oldImageSize = [[[window contentView] image] size]; [[window contentView] setImageData:(CvArr *)arr]; if([window autosize] || [window firstContent] || empty) { - //Set new view size considering sliders (reserve height and min width) - NSRect vrectNew = vrectOld; - int slider_height = 0; - if ([window respondsToSelector:@selector(sliders)]) { - for(NSString *key in [window sliders]) { - slider_height += [[[window sliders] valueForKey:key] frame].size.height; + NSSize imageSize = [[[window contentView] image] size]; + // Only adjust the image size if the new image is a different size from the previous + if (oldImageSize.height != imageSize.height || oldImageSize.width != imageSize.width) + { + //Set new view size considering sliders (reserve height and min width) + NSSize scaledImageSize; + if ([[window contentView] respondsToSelector:@selector(convertSizeFromBacking:)]) + { + // Only resize for retina displays if the image is bigger than the screen + NSSize screenSize = NSScreen.mainScreen.visibleFrame.size; + CGFloat titleBarHeight = window.frame.size.height - [window contentRectForFrameRect:window.frame].size.height; + screenSize.height -= titleBarHeight; + if (imageSize.width > screenSize.width || imageSize.height > screenSize.height) + { + scaledImageSize = [[window contentView] convertSizeFromBacking:imageSize]; + } + } + else + { + scaledImageSize = imageSize; } + NSSize contentSize = vrectOld.size; + contentSize.height = scaledImageSize.height + [window contentView].sliderHeight; + contentSize.width = std::max(scaledImageSize.width, MIN_SLIDER_WIDTH); + [window setContentSize:contentSize]; //adjust sliders to fit new window size } - vrectNew.size.height = [[[window contentView] image] size].height + slider_height; - vrectNew.size.width = std::max([[[window contentView] image] size].width, MIN_SLIDER_WIDTH); - [[window contentView] setFrameSize:vrectNew.size]; //adjust sliders to fit new window size - - rect.size.width += vrectNew.size.width - vrectOld.size.width; - rect.size.height += vrectNew.size.height - vrectOld.size.height; - rect.origin.y -= vrectNew.size.height - vrectOld.size.height; - - [window setFrame:rect display:YES]; } - else - [window display]; [window setFirstContent:NO]; } [localpool drain]; @@ -259,10 +264,9 @@ CV_IMPL void cvResizeWindow( const char* name, int width, int height) NSAutoreleasePool* localpool = [[NSAutoreleasePool alloc] init]; CVWindow *window = cvGetWindow(name); if(window && ![window autosize]) { - NSRect frame = [window frame]; - frame.size.width = width; - frame.size.height = height; - [window setFrame:frame display:YES]; + height += [window contentView].sliderHeight; + NSSize size = { width, height }; + [window setContentSize:size]; } [localpool drain]; } @@ -532,7 +536,7 @@ CV_IMPL int cvNamedWindow( const char* name, int flags ) NSScreen* mainDisplay = [NSScreen mainScreen]; NSString *windowName = [NSString stringWithFormat:@"%s", name]; - NSUInteger showResize = (flags == CV_WINDOW_AUTOSIZE) ? 0: NSResizableWindowMask ; + NSUInteger showResize = NSResizableWindowMask; NSUInteger styleMask = NSTitledWindowMask|NSMiniaturizableWindowMask|showResize; CGFloat windowWidth = [NSWindow minFrameWidthWithTitle:windowName styleMask:styleMask]; NSRect initContentRect = NSMakeRect(0, 0, windowWidth, 0); @@ -728,6 +732,22 @@ void cv::setWindowTitle(const String& winname, const String& title) [localpool drain]; } +static NSSize constrainAspectRatio(NSSize base, NSSize constraint) { + CGFloat heightDiff = (base.height / constraint.height); + CGFloat widthDiff = (base.width / constraint.width); + if (widthDiff == heightDiff) { + return base; + } + else if (widthDiff > heightDiff) { + NSSize out = { constraint.width / constraint.height * base.height, base.height }; + return out; + } + else { + NSSize out = { base.width, constraint.height / constraint.width * base.width }; + return out; + } +} + @implementation CVWindow @synthesize mouseCallback; @@ -743,22 +763,19 @@ void cv::setWindowTitle(const String& winname, const String& title) NSPoint mp = [NSEvent mouseLocation]; //NSRect visible = [[self contentView] frame]; mp = [self convertScreenToBase: mp]; - double viewHeight = [self contentView].frame.size.height; - double viewWidth = [self contentView].frame.size.width; - CVWindow *window = (CVWindow *)[[self contentView] window]; - if ([window respondsToSelector:@selector(sliders)]) { - for(NSString *key in [window sliders]) { - NSSlider *slider = [[window sliders] valueForKey:key]; - viewHeight = std::min(viewHeight, (double)([slider frame].origin.y)); - } + CVView *contentView = [self contentView]; + NSSize viewSize = contentView.frame.size; + if (contentView.imageView) { + viewSize = contentView.imageView.frame.size; + } + else { + viewSize.height -= contentView.sliderHeight; } - viewHeight -= TOP_BORDER; - mp.y = viewHeight - mp.y; + mp.y = viewSize.height - mp.y; - NSImage* image = ((CVView*)[self contentView]).image; - NSSize imageSize = [image size]; - mp.x = mp.x * imageSize.width / std::max(viewWidth, 1.); - mp.y = mp.y * imageSize.height / std::max(viewHeight, 1.); + NSSize imageSize = contentView.image.size; + mp.y *= (imageSize.height / std::max(viewSize.height, 1.)); + mp.x *= (imageSize.width / std::max(viewSize.width, 1.)); if( mp.x >= 0 && mp.y >= 0 && mp.x < imageSize.width && mp.y < imageSize.height ) mouseCallback(type, mp.x, mp.y, flags, mouseParam); @@ -862,17 +879,14 @@ void cv::setWindowTitle(const String& winname, const String& title) viewSize.width = std::max(viewSize.width, MIN_SLIDER_WIDTH); // Update slider sizes - [[self contentView] setFrameSize:viewSize]; - [[self contentView] setNeedsDisplay:YES]; - - //update window size to contain sliders - NSRect rect = [self frame]; - rect.size.height += [slider frame].size.height; - rect.size.width = std::max(rect.size.width, MIN_SLIDER_WIDTH); - [self setFrame:rect display:YES]; - + [self contentView].sliderHeight += sliderSize.height; + if ([[self contentView] image] && ![[self contentView] imageView]) { + [[self contentView] setNeedsDisplay:YES]; + } + //update window size to contain sliders + [self setContentSize: viewSize]; } - (CVView *)contentView { @@ -888,20 +902,15 @@ void cv::setWindowTitle(const String& winname, const String& title) - (id)init { //cout << "CVView init" << endl; [super init]; - image = [[NSImage alloc] init]; return self; } - (void)setImageData:(CvArr *)arr { //cout << "setImageData" << endl; NSAutoreleasePool* localpool = [[NSAutoreleasePool alloc] init]; - CvMat *arrMat, *cvimage, stub; + CvMat *arrMat, dst, stub; arrMat = cvGetMat(arr, &stub); - - cvimage = cvCreateMat(arrMat->rows, arrMat->cols, CV_8UC3); - cvConvertImage(arrMat, cvimage, CV_CVTIMG_SWAP_RB); - /*CGColorSpaceRef colorspace = NULL; CGDataProviderRef provider = NULL; int width = cvimage->width; @@ -922,42 +931,77 @@ void cv::setWindowTitle(const String& winname, const String& title) }*/ NSBitmapImageRep *bitmap = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:NULL - pixelsWide:cvimage->width - pixelsHigh:cvimage->height + pixelsWide:arrMat->cols + pixelsHigh:arrMat->rows bitsPerSample:8 samplesPerPixel:3 hasAlpha:NO isPlanar:NO colorSpaceName:NSDeviceRGBColorSpace - bytesPerRow:(cvimage->width * 4) - bitsPerPixel:32]; - - int pixelCount = cvimage->width * cvimage->height; - unsigned char *src = cvimage->data.ptr; - unsigned char *dst = [bitmap bitmapData]; + bitmapFormat: kCGImageAlphaNone + bytesPerRow:((arrMat->cols * 3 + 3) & -4) + bitsPerPixel:24]; - for( int i = 0; i < pixelCount; i++ ) - { - dst[i * 4 + 0] = src[i * 3 + 0]; - dst[i * 4 + 1] = src[i * 3 + 1]; - dst[i * 4 + 2] = src[i * 3 + 2]; + if (bitmap) { + cvInitMatHeader(&dst, arrMat->rows, arrMat->cols, CV_8UC3, [bitmap bitmapData], [bitmap bytesPerRow]); + cvConvertImage(arrMat, &dst, CV_CVTIMG_SWAP_RB); + } + else { + // It's not guaranteed to like the bitsPerPixel:24, but this is a lot slower so we'd rather not do it + bitmap = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:NULL + pixelsWide:arrMat->cols + pixelsHigh:arrMat->rows + bitsPerSample:8 + samplesPerPixel:3 + hasAlpha:NO + isPlanar:NO + colorSpaceName:NSDeviceRGBColorSpace + bytesPerRow:(arrMat->cols * 4) + bitsPerPixel:32]; + uint8_t *data = [bitmap bitmapData]; + cvInitMatHeader(&dst, arrMat->rows, arrMat->cols, CV_8UC3, data, (arrMat->cols * 3)); + cvConvertImage(arrMat, &dst, CV_CVTIMG_SWAP_RB); + for (int i = (arrMat->rows * arrMat->cols) - 1; i >= 0; i--) { + memmove(data + i * 4, data + i * 3, 3); + data[i * 4 + 3] = 0; + } } - if( image ) + if( image ) { [image release]; + } image = [[NSImage alloc] init]; [image addRepresentation:bitmap]; [bitmap release]; + // This isn't supported on older versions of macOS + // The performance issues this solves are mainly on newer versions of macOS, so that's fine + if( floor(NSAppKitVersionNumber) > NSAppKitVersionNumber10_5 ) { + if (![self imageView]) { + [self setImageView:[[NSView alloc] init]]; + [[self imageView] setWantsLayer:true]; + [self addSubview:[self imageView]]; + } + + [[[self imageView] layer] setContents:image]; + + NSRect imageViewFrame = [self frame]; + imageViewFrame.size.height -= [self sliderHeight]; + NSRect constrainedFrame = { imageViewFrame.origin, constrainAspectRatio(imageViewFrame.size, [image size]) }; + [[self imageView] setFrame:constrainedFrame]; + } + else { + NSRect redisplayRect = [self frame]; + redisplayRect.size.height -= [self sliderHeight]; + [self setNeedsDisplayInRect:redisplayRect]; + } + /*CGColorSpaceRelease(colorspace); CGDataProviderRelease(provider); CGImageRelease(imageRef);*/ - cvReleaseMat(&cvimage); - [localpool drain]; - - [self setNeedsDisplay:YES]; + [localpool drain]; } - (void)setFrameSize:(NSSize)size { @@ -970,41 +1014,49 @@ void cv::setWindowTitle(const String& winname, const String& title) CVWindow *cvwindow = (CVWindow *)[self window]; if ([cvwindow respondsToSelector:@selector(sliders)]) { for(NSString *key in [cvwindow sliders]) { - NSSlider *slider = [[cvwindow sliders] valueForKey:key]; + CVSlider *slider = [[cvwindow sliders] valueForKey:key]; NSRect r = [slider frame]; r.origin.y = height - r.size.height; r.size.width = [[cvwindow contentView] frame].size.width; + + CGRect sliderRect = slider.slider.frame; + CGFloat targetWidth = r.size.width - (sliderRect.origin.x + 10); + sliderRect.size.width = targetWidth < 0 ? 0 : targetWidth; + slider.slider.frame = sliderRect; + [slider setFrame:r]; height -= r.size.height; } } + NSRect frame = self.frame; + if (frame.size.height < self.sliderHeight) { + frame.size.height = self.sliderHeight; + self.frame = frame; + } + if ([self imageView]) { + NSRect imageViewFrame = frame; + imageViewFrame.size.height -= [self sliderHeight]; + NSRect constrainedFrame = { imageViewFrame.origin, constrainAspectRatio(imageViewFrame.size, [image size]) }; + [[self imageView] setFrame:constrainedFrame]; + } [localpool drain]; } - (void)drawRect:(NSRect)rect { //cout << "drawRect" << endl; [super drawRect:rect]; - - NSAutoreleasePool* localpool = [[NSAutoreleasePool alloc] init]; - CVWindow *cvwindow = (CVWindow *)[self window]; - int height = 0; - if ([cvwindow respondsToSelector:@selector(sliders)]) { - for(NSString *key in [cvwindow sliders]) { - height += [[[cvwindow sliders] valueForKey:key] frame].size.height; + // If imageView exists, all drawing will be done by it and nothing needs to happen here + if ([self image] && ![self imageView]) { + NSAutoreleasePool* localpool = [[NSAutoreleasePool alloc] init]; + + if(image != nil) { + [image drawInRect: [self frame] + fromRect: NSZeroRect + operation: NSCompositeSourceOver + fraction: 1.0]; } + [localpool release]; } - - - NSRect imageRect = {{0,0}, {[image size].width, [image size].height}}; - - if(image != nil) { - [image drawInRect: imageRect - fromRect: NSZeroRect - operation: NSCompositeSourceOver - fraction: 1.0]; - } - [localpool release]; - } @end