mirror of
https://github.com/opencv/opencv.git
synced 2025-06-11 11:45:30 +08:00
Merge pull request #12697 from tellowkrinkle:FasterCocoaWindows
* Make cocoa windows draw faster * Use a CALayer for rendering when possible Uses GPU to scale images, which is important because retina macs will want window sizes much larger (in pixels) than the image * Fix mouse logic for cocoa windows * Only halve resolution on retina if image is larger than display
This commit is contained in:
parent
824241ad5c
commit
803ff64b14
@ -74,7 +74,6 @@ CV_IMPL int cvWaitKey (int maxWait) {return 0;}
|
||||
|
||||
#include <iostream>
|
||||
|
||||
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<int>(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<int>([[[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;
|
||||
}
|
||||
viewHeight -= TOP_BORDER;
|
||||
mp.y = viewHeight - mp.y;
|
||||
else {
|
||||
viewSize.height -= contentView.sliderHeight;
|
||||
}
|
||||
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<int>(viewSize.width, MIN_SLIDER_WIDTH);
|
||||
|
||||
// Update slider sizes
|
||||
[[self contentView] setFrameSize:viewSize];
|
||||
[[self contentView] setNeedsDisplay:YES];
|
||||
[self contentView].sliderHeight += sliderSize.height;
|
||||
|
||||
if ([[self contentView] image] && ![[self contentView] imageView]) {
|
||||
[[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<int>(rect.size.width, MIN_SLIDER_WIDTH);
|
||||
[self setFrame:rect display:YES];
|
||||
|
||||
|
||||
|
||||
[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];
|
||||
bitmapFormat: kCGImageAlphaNone
|
||||
bytesPerRow:((arrMat->cols * 3 + 3) & -4)
|
||||
bitsPerPixel:24];
|
||||
|
||||
int pixelCount = cvimage->width * cvimage->height;
|
||||
unsigned char *src = cvimage->data.ptr;
|
||||
unsigned char *dst = [bitmap bitmapData];
|
||||
|
||||
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];
|
||||
|
||||
}
|
||||
|
||||
- (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];
|
||||
// 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];
|
||||
|
||||
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(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
|
||||
|
Loading…
Reference in New Issue
Block a user