Mat+QuickLook.mm 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. //
  2. // Mat+QuickLook.mm
  3. //
  4. // Created by Giles Payne on 2021/07/18.
  5. //
  6. #import "Mat+QuickLook.h"
  7. #import "Mat+Converters.h"
  8. #import "Rect2i.h"
  9. #import "Core.h"
  10. #import "Imgproc.h"
  11. #import <opencv2/imgcodecs/macosx.h>
  12. #define SIZE 20
  13. static NSFont* getCMU() {
  14. return [NSFont fontWithName:@"CMU Serif" size:SIZE];
  15. }
  16. static NSFont* getBodoni72() {
  17. return [NSFont fontWithName:@"Bodoni 72" size:SIZE];
  18. }
  19. static NSFont* getAnySerif() {
  20. #if defined(__MAC_OS_X_VERSION_MAX_ALLOWED) && __MAC_OS_X_VERSION_MAX_ALLOWED >= 110000
  21. if (@available(macOS 11.0, *)) {
  22. return [NSFont fontWithDescriptor:[[NSFontDescriptor preferredFontDescriptorForTextStyle:NSFontTextStyleBody options:@{}] fontDescriptorWithDesign:NSFontDescriptorSystemDesignSerif] size:SIZE];
  23. } else {
  24. return nil;
  25. }
  26. #else
  27. return nil;
  28. #endif
  29. }
  30. static NSFont* getSystemFont() {
  31. return [NSFont systemFontOfSize:SIZE];
  32. }
  33. typedef NSFont* (*FontGetter)();
  34. @implementation Mat (QuickLook)
  35. - (NSString*)makeLabel:(BOOL)isIntType val:(NSNumber*)num {
  36. if (isIntType) {
  37. return [NSString stringWithFormat:@"%d", num.intValue];
  38. } else {
  39. int exponent = 1 + (int)log10(abs(num.doubleValue));
  40. if (num.doubleValue == (double)num.intValue && num.doubleValue < 10000 && num.doubleValue > -10000) {
  41. return [NSString stringWithFormat:@"%d", num.intValue];;
  42. } else if (exponent <= 5 && exponent >= -1) {
  43. return [NSString stringWithFormat:[NSString stringWithFormat:@"%%%d.%df", 6, MIN(5 - exponent, 4)], num.doubleValue];
  44. } else {
  45. return [[[NSString stringWithFormat:@"%.2e", num.doubleValue] stringByReplacingOccurrencesOfString:@"e+0" withString:@"e"] stringByReplacingOccurrencesOfString:@"e-0" withString:@"e-"];
  46. }
  47. }
  48. }
  49. - (id)debugQuickLookObject {
  50. // for smallish Mat objects display as a matrix
  51. if ([self dims] == 2 && [self rows] <= 10 && [self cols] <= 10) {
  52. FontGetter fontGetters[] = { getCMU, getBodoni72, getAnySerif, getSystemFont };
  53. NSFont* font = nil;
  54. for (int fontGetterIndex = 0; font==nil && fontGetterIndex < (sizeof(fontGetters)) / (sizeof(fontGetters[0])); fontGetterIndex++) {
  55. font = fontGetters[fontGetterIndex]();
  56. }
  57. int elements = [self rows] * [self cols];
  58. NSDictionary<NSAttributedStringKey,id>* textFontAttributes = @{ NSFontAttributeName: font, NSForegroundColorAttributeName: NSColor.blackColor };
  59. NSMutableArray<NSNumber*>* rawData = [NSMutableArray new];
  60. for (int dataIndex = 0; dataIndex < elements; dataIndex++) {
  61. [rawData addObject:[NSNumber numberWithDouble:0]];
  62. }
  63. [self get:0 col: 0 data: rawData];
  64. BOOL isIntType = [self depth] <= CV_32S;
  65. NSMutableArray<NSString*>* labels = [NSMutableArray new];
  66. NSMutableDictionary<NSString*, NSValue*>* boundingRects = [NSMutableDictionary dictionaryWithCapacity:elements];
  67. int maxWidth = 0, maxHeight = 0;
  68. for (NSNumber* number in rawData) {
  69. NSString* label = [self makeLabel:isIntType val:number];
  70. [labels addObject:label];
  71. NSRect boundingRect = [label boundingRectWithSize:NSMakeSize(CGFLOAT_MAX, CGFLOAT_MAX) options:NSStringDrawingUsesLineFragmentOrigin attributes:textFontAttributes];
  72. if (boundingRect.size.width > maxWidth) {
  73. maxWidth = boundingRect.size.width;
  74. }
  75. if (boundingRect.size.height > maxHeight) {
  76. maxHeight = boundingRect.size.height;
  77. }
  78. boundingRects[label] = [NSValue valueWithRect:boundingRect];
  79. }
  80. int rowGap = 8;
  81. int colGap = 8;
  82. int borderGap = 9;
  83. int lineThickness = 4;
  84. int lipWidth = 8;
  85. int imageWidth = 2 * (borderGap + lipWidth) + maxWidth * [self cols] + colGap * ([self cols] - 1);
  86. int imageHeight = 2 * (borderGap + lipWidth) + maxHeight * [self rows] + rowGap * ([self rows] - 1);
  87. NSImage* image = [[NSImage alloc] initWithSize:NSMakeSize(imageWidth, imageHeight)];
  88. NSBezierPath* leftBracket = [NSBezierPath new];
  89. [leftBracket moveToPoint:NSMakePoint(borderGap, borderGap)];
  90. [leftBracket relativeLineToPoint:NSMakePoint(0, imageHeight - 2 * borderGap)];
  91. [leftBracket relativeLineToPoint:NSMakePoint(lineThickness + lipWidth, 0)];
  92. [leftBracket relativeLineToPoint:NSMakePoint(0, -lineThickness)];
  93. [leftBracket relativeLineToPoint:NSMakePoint(-lipWidth, 0)];
  94. [leftBracket relativeLineToPoint:NSMakePoint(0, -(imageHeight - 2 * (borderGap + lineThickness)))];
  95. [leftBracket relativeLineToPoint:NSMakePoint(lipWidth, 0)];
  96. [leftBracket relativeLineToPoint:NSMakePoint(0, -lineThickness)];
  97. [leftBracket relativeLineToPoint:NSMakePoint(-(lineThickness + lipWidth), 0)];
  98. NSAffineTransform* reflect = [NSAffineTransform new];
  99. [reflect scaleXBy:-1 yBy:1];
  100. [reflect translateXBy:-imageWidth yBy:0];
  101. NSBezierPath* rightBracket = [leftBracket copy];
  102. [rightBracket transformUsingAffineTransform:reflect];
  103. [image lockFocus];
  104. [NSColor.whiteColor drawSwatchInRect:NSMakeRect(0, 0, imageWidth, imageHeight)];
  105. [NSColor.blackColor set];
  106. [leftBracket fill];
  107. [rightBracket fill];
  108. [labels enumerateObjectsUsingBlock:^(id label, NSUInteger index, BOOL *stop)
  109. {
  110. NSRect boundingRect = boundingRects[label].rectValue;
  111. int row = [self rows] - 1 - ((int)index / [self cols]);
  112. int col = (int)index % [self cols];
  113. int x = borderGap + lipWidth + col * (maxWidth + colGap) + (maxWidth - boundingRect.size.width) / 2;
  114. int y = borderGap + lipWidth + row * (maxHeight + rowGap) + (maxHeight - boundingRect.size.height) / 2;
  115. NSRect textRect = NSMakeRect(x, y, boundingRect.size.width, boundingRect.size.height);
  116. [label drawInRect:textRect withAttributes:textFontAttributes];
  117. }];
  118. [image unlockFocus];
  119. return image;
  120. } else if (([self dims] == 2) && ([self type] == CV_8U || [self type] == CV_8UC3 || [self type] == CV_8UC4)) {
  121. // convert to NSImage if the Mats has 2 dimensions and a type and number of channels consistent with it being a image
  122. return [self toNSImage];
  123. } else if ([self dims] == 2 && [self channels] == 1) {
  124. // for other Mats with 2 dimensions and one channel - generate heat map
  125. Mat* normalized = [Mat new];
  126. [Core normalize:self dst:normalized alpha:0 beta:255 norm_type:NORM_MINMAX dtype:CV_8U];
  127. Mat* normalizedKey = [[Mat alloc] initWithRows:[self rows] + 10 cols:[self cols] type:CV_8U];
  128. std::vector<char> key;
  129. for (int index = 0; index < [self cols]; index++) {
  130. key.push_back((char)(index * 256 / [self cols]));
  131. }
  132. for (int index = 0; index < 10; index++) {
  133. [normalizedKey put:@[[NSNumber numberWithInt:index], [NSNumber numberWithInt:0]] count:[self cols] byteBuffer:key.data()];
  134. }
  135. [normalized copyTo:[normalizedKey submatRoi:[[Rect2i alloc] initWithX:0 y:10 width:[self cols] height:[self rows]]]];
  136. Mat* colorMap = [Mat new];
  137. [Imgproc applyColorMap:normalizedKey dst:colorMap colormap:COLORMAP_JET];
  138. [Imgproc cvtColor:colorMap dst:colorMap code:COLOR_BGR2RGB];
  139. return [colorMap toNSImage];
  140. }
  141. //everything just return the Mat description
  142. return [self description];
  143. }
  144. @end