VapxLayoutManager.m 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389
  1. // Tencent is pleased to support the open source community by making vap available.
  2. //
  3. // Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved.
  4. //
  5. // Licensed under the MIT License (the "License"); you may not use this file except in
  6. // compliance with the License. You may obtain a copy of the License at
  7. //
  8. // http://opensource.org/licenses/MIT
  9. //
  10. // Unless required by applicable law or agreed to in writing, software distributed under the License is
  11. // distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
  12. // either express or implied. See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. #import "VapxLayoutManager.h"
  15. @interface VapxLayoutItem : NSObject
  16. @property (nonatomic, readonly) NSSize size;
  17. @property (nonatomic, strong) NSImage *image;
  18. @property (nonatomic, assign) CGFloat scale;
  19. @property (nonatomic, assign) BOOL isAlpha;
  20. @property (nonatomic, assign) BOOL isRGB;
  21. @property (nonatomic, strong) QGVAPMergedInfo *mergeInfo;
  22. @end
  23. @implementation VapxLayoutItem
  24. - (NSSize)size {
  25. if (self.scale > 0) {
  26. return NSMakeSize(self.image.size.width*self.scale, self.image.size.height*self.scale);
  27. }
  28. return self.image.size;
  29. }
  30. @end
  31. @implementation VapxBox
  32. @end
  33. @implementation VapxLayoutManager
  34. - (instancetype)init {
  35. if (self = [super init]) {
  36. _padding = NSEdgeInsetsZero;
  37. }
  38. return self;
  39. }
  40. - (void)layoutWith:(QGVAPConfigModel *)config desDir:(NSString *)des alphaScale:(CGFloat)scale {
  41. NSFileManager *fileManager = [NSFileManager defaultManager];
  42. if (![fileManager fileExistsAtPath:des]) {
  43. NSError *error = nil;
  44. [fileManager createDirectoryAtPath:des withIntermediateDirectories:YES attributes:nil error:&error];
  45. if (error) {
  46. NSLog(@"error create dir fail:%@", des);
  47. return ;
  48. }
  49. }
  50. CGSize size = config.info.videoSize;
  51. //每一帧
  52. [config.info.alphaPaths enumerateObjectsUsingBlock:^(NSString *alphaPath, NSUInteger idx, BOOL * _Nonnull stop) {
  53. @autoreleasepool {
  54. NSMutableArray *layoutItems = [NSMutableArray new];
  55. VapxBox *rootBox = [VapxBox new];
  56. rootBox.rect = NSMakeRect(0, 0, size.width, size.height);
  57. NSImage *alphaImage = [[NSImage alloc] initWithContentsOfFile:alphaPath];
  58. NSImage *rgbImage = [[NSImage alloc] initWithContentsOfFile:config.info.rgbPaths[idx]];
  59. VapxLayoutItem *alphaItem = [VapxLayoutItem new];
  60. alphaItem.image = alphaImage ;
  61. alphaItem.scale = scale;
  62. alphaItem.isAlpha = YES;
  63. VapxLayoutItem *rgbItem = [VapxLayoutItem new];
  64. rgbItem = [VapxLayoutItem new];
  65. rgbItem.image = rgbImage;
  66. rgbItem.isRGB = YES;
  67. [layoutItems addObject:alphaItem];
  68. [layoutItems addObject:rgbItem];
  69. //当前帧的每一个mask
  70. [config.mergedConfig[@(idx)] enumerateObjectsUsingBlock:^(QGVAPMergedInfo * _Nonnull maskInfo, NSUInteger idx, BOOL * _Nonnull stop) {
  71. NSString *maskPath = maskInfo.tempPathForMask;
  72. NSImage *maskImage = [[NSImage alloc] initWithContentsOfFile:maskPath];
  73. VapxLayoutItem *maskItem = [VapxLayoutItem new];
  74. maskItem.image = maskImage;
  75. maskItem.mergeInfo = maskInfo;
  76. [layoutItems addObject:maskItem];
  77. }];
  78. NSImage *mergedImage = [self layoutWith:config desDir:des layoutItems:layoutItems];
  79. NSString *path = [des stringByAppendingPathComponent:[alphaPath lastPathComponent]];
  80. [self saveImage:mergedImage atPath:path];
  81. }
  82. }];
  83. }
  84. - (NSImage *)layoutWith:(QGVAPConfigModel *)config desDir:(NSString *)des layoutItems:(NSArray<VapxLayoutItem*>*)items {
  85. CGSize size = config.info.videoSize;
  86. CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
  87. NSUInteger bytesPerPixel = 4;
  88. NSUInteger bitsPerComponent = 8;
  89. CGContextRef mergeContext = CGBitmapContextCreate(nil, (int)size.width, (int)size.height,
  90. bitsPerComponent, (int)(bytesPerPixel*size.width), colorSpace,
  91. kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
  92. __block VapxLayoutItem *rgbItem = nil;
  93. __block VapxLayoutItem *alphaItem = nil;
  94. NSMutableArray *sortedItems = [[items sortedArrayUsingComparator:^NSComparisonResult(VapxLayoutItem *obj1, VapxLayoutItem *obj2) {
  95. CGSize size1 = obj1.size;
  96. CGSize size2 = obj2.size;
  97. return [@(size2.width*size2.height) compare:@(size1.width*size1.height)];
  98. }] mutableCopy];
  99. [sortedItems enumerateObjectsUsingBlock:^(VapxLayoutItem *obj, NSUInteger idx, BOOL * _Nonnull stop) {
  100. if (obj.isAlpha) {
  101. alphaItem = obj;
  102. }
  103. if (obj.isRGB) {
  104. rgbItem = obj;
  105. }
  106. }];
  107. [sortedItems removeObject:alphaItem];
  108. [sortedItems insertObject:alphaItem atIndex:0];
  109. [sortedItems removeObject:rgbItem];
  110. [sortedItems insertObject:rgbItem atIndex:0];
  111. VapxBox *rootBox = [VapxBox new];
  112. rootBox.rect = NSMakeRect(0, 0, size.width, size.height);
  113. //宽型视频
  114. NSSize firstSize = fitOuterSize([sortedItems.firstObject size], self.padding);
  115. BOOL rightFirst = (firstSize.width >= firstSize.height);
  116. [sortedItems enumerateObjectsUsingBlock:^(VapxLayoutItem *item, NSUInteger idx, BOOL * _Nonnull stop) {
  117. NSSize imageSize = item.size;
  118. NSSize imageOuterSize = fitOuterSize(imageSize, self.padding);
  119. VapxBox *box = [self findBox:rootBox size:imageOuterSize rightFirst:rightFirst];
  120. if (box) {
  121. [self splitBox:box size:imageOuterSize];
  122. NSEdgeInsets fitPadding = paddingForSize(imageSize, self.padding);
  123. NSRect drawRect = NSMakeRect(box.rect.origin.x+fitPadding.left, size.height - box.rect.origin.y - fitPadding.top - imageSize.height, imageSize.width, imageSize.height);
  124. NSRect drawBounds = NSMakeRect(0, 0, imageSize.width, imageSize.height);
  125. //debug code
  126. // CGContextSetFillColorWithColor(mergeContext, [NSColor redColor].CGColor);
  127. // CGContextFillRect(mergeContext, NSMakeRect(box.rect.origin.x, size.height - box.rect.origin.y - imageOuterSize.height, imageOuterSize.width, imageOuterSize.height));
  128. CGContextDrawImage(mergeContext, drawRect, [item.image CGImageForProposedRect:&drawBounds context:nil hints:nil]);
  129. NSRect rect = NSMakeRect(box.rect.origin.x+fitPadding.left, box.rect.origin.y+fitPadding.top, imageSize.width, imageSize.height);
  130. if (item.isAlpha) {
  131. config.info.alphaAreaRect = rect;
  132. } else if (item.isRGB) {
  133. config.info.rgbAreaRect = rect;
  134. } else {
  135. item.mergeInfo.maskRect = rect;
  136. }
  137. }
  138. }];
  139. CGImageRef mergedImageRef = CGBitmapContextCreateImage(mergeContext);
  140. NSImage *mergedImage = [[NSImage alloc] initWithCGImage:mergedImageRef size:size];
  141. CGImageRelease(mergedImageRef);
  142. CGContextRelease(mergeContext);
  143. return mergedImage;
  144. }
  145. - (void)saveImage:(NSImage *)image atPath:(NSString *)path {
  146. CGImageRef cgRef = [image CGImageForProposedRect:NULL
  147. context:nil
  148. hints:nil];
  149. NSBitmapImageRep *newRep = [[NSBitmapImageRep alloc] initWithCGImage:cgRef];
  150. [newRep setSize:[image size]]; // if you want the same resolution
  151. NSData *pngData = [newRep representationUsingType:NSBitmapImageFileTypePNG properties:@{}];
  152. BOOL succ = [pngData writeToFile:path atomically:YES];
  153. if (!succ) {
  154. }
  155. }
  156. - (NSSize)maximumSizeForInfo:(QGVAPConfigModel *)config alphaMinScale:(CGFloat)scale {
  157. __block NSSize minSize = NSMakeSize(0, 0);
  158. [config.info.alphaPaths enumerateObjectsUsingBlock:^(NSString *obj, NSUInteger idx, BOOL * _Nonnull stop) {
  159. NSMutableArray *sizes = [NSMutableArray new];
  160. NSSize imageSize = [self sizeOfImageAt:config.info.alphaPaths[idx]];
  161. NSSize alphaSize = NSMakeSize(imageSize.width*scale, imageSize.height*scale);
  162. [sizes addObject:[NSValue valueWithSize:fitOuterSize(imageSize, self.padding)]];
  163. [sizes addObject:[NSValue valueWithSize:fitOuterSize(alphaSize, self.padding)]];
  164. [config.mergedConfig[@(idx)] enumerateObjectsUsingBlock:^(QGVAPMergedInfo * _Nonnull maskInfo, NSUInteger idx, BOOL * _Nonnull stop) {
  165. NSSize maskSize = [self sizeOfImageAt:maskInfo.tempPathForMask];
  166. //NSSize rotatedSize = [self rotatedSize:maskSize];
  167. [sizes addObject:[NSValue valueWithSize:fitOuterSize(maskSize, self.padding)]];
  168. }];
  169. NSSize size = [self mergeSizes:sizes];
  170. minSize = ((minSize.width*minSize.height) > (size.width*size.height)) ? minSize : size;
  171. }];
  172. return minSize;
  173. }
  174. - (NSSize)rotatedSize:(NSSize)size {
  175. return NSMakeSize(size.height, size.width);
  176. }
  177. - (NSImage *)rotatedImage:(NSImage *)image {
  178. CGFloat width = image.size.width;
  179. CGFloat height = image.size.height;
  180. NSRect rect = NSMakeRect(0, 0, width, height);
  181. CGImageRef imageRef = [image CGImageForProposedRect:&rect context:nil hints:nil];
  182. CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
  183. unsigned char *rawData = (unsigned char*)calloc(height * width * 4, sizeof(unsigned char));
  184. NSUInteger bytesPerPixel = 4;
  185. NSUInteger bytesPerRow = bytesPerPixel * width;
  186. NSUInteger bitsPerComponent = 8;
  187. CGContextRef context = CGBitmapContextCreate(rawData, width, height,
  188. bitsPerComponent, bytesPerRow, colorSpace,
  189. kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
  190. CGColorSpaceRelease(colorSpace);
  191. CGContextDrawImage(context, CGRectMake(0, 0, width, height), imageRef);
  192. CGContextRelease(context);
  193. CGFloat newWidth = height;
  194. CGFloat newHeight = width;
  195. unsigned char *rotatedData = (unsigned char*)calloc(height * width * 4, sizeof(unsigned char));
  196. //旋转计算
  197. for (int i = 0 ; i < newHeight; i ++) {
  198. for (int j = 0; j < newWidth; j ++) {
  199. NSInteger oldIndex = ((width-1)-i+j*width)*4;
  200. NSInteger newIndex = (i*(int)newWidth+j)*4;
  201. rotatedData[newIndex] = rawData[oldIndex];
  202. rotatedData[newIndex+1] = rawData[oldIndex+1];
  203. rotatedData[newIndex+2] = rawData[oldIndex+2];
  204. rotatedData[newIndex+3] = rawData[oldIndex+3];
  205. }
  206. }
  207. free(rawData);
  208. CGDataProviderRef rotatedProvider = CGDataProviderCreateWithData(NULL, rotatedData, newWidth*newHeight*4, NULL);
  209. CGColorSpaceRef rotateColorSpace = CGColorSpaceCreateDeviceRGB();
  210. CGBitmapInfo bitmapInfo = kCGBitmapByteOrderDefault | (CGBitmapInfo)kCGImageAlphaLast;
  211. CGColorRenderingIntent renderingIntent = kCGRenderingIntentDefault;
  212. CGImageRef maskImageRef = CGImageCreate(newWidth, newHeight, 8, 32, 4*newWidth,rotateColorSpace, bitmapInfo, rotatedProvider,NULL,NO, renderingIntent);
  213. NSImage *rotatedImage = [[NSImage alloc] initWithCGImage:maskImageRef size:NSMakeSize(newWidth, newHeight)];
  214. CGDataProviderRelease(rotatedProvider);
  215. CGImageRelease(maskImageRef);
  216. free(rotatedData);
  217. return rotatedImage;
  218. }
  219. NSSize fitOuterSize(NSSize size, NSEdgeInsets padding) {
  220. NSEdgeInsets newPadding = paddingForSize(size, padding);
  221. return outerSize(size, newPadding);
  222. }
  223. NSRect fitInnerRect(NSRect rect, NSEdgeInsets padding) {
  224. NSEdgeInsets newPadding = paddingForSize(rect.size, padding);
  225. return innerRect(rect, newPadding);
  226. }
  227. NSSize outerSize(NSSize size, NSEdgeInsets padding) {
  228. return NSMakeSize(size.width+padding.left+padding.right, size.height+padding.top+padding.bottom);
  229. }
  230. NSRect innerRect(NSRect rect, NSEdgeInsets padding) {
  231. return NSMakeRect(rect.origin.x+padding.left, rect.origin.y+padding.top, rect.size.width-padding.left-padding.right, rect.size.height-padding.top-padding.bottom);
  232. }
  233. NSEdgeInsets paddingForSize(NSSize size, NSEdgeInsets padding) {
  234. NSEdgeInsets newPadding = padding;
  235. if (size.width + padding.left + padding.right > kVapLayoutMaxWidth) {
  236. newPadding.left = 0;
  237. newPadding.right = 0;
  238. }
  239. if (size.height + padding.top + padding.bottom > kVapLayoutMaxWidth) {
  240. newPadding.top = 0;
  241. newPadding.bottom = 0;
  242. }
  243. return newPadding;
  244. }
  245. - (NSSize)sizeOfImageAt:(NSString *)imagePath {
  246. NSImage *image = [[NSImage alloc] initWithContentsOfFile:imagePath];
  247. return image.size;
  248. }
  249. - (NSSize)mergeSizes:(NSArray *)sizes {
  250. CGSize videoSize = [sizes[0] sizeValue];
  251. NSArray *sortedSizes = [sizes sortedArrayUsingComparator:^NSComparisonResult(NSValue *obj1, NSValue *obj2) {
  252. CGSize size1 = [obj1 sizeValue];
  253. CGSize size2 = [obj2 sizeValue];
  254. return [@(size2.width*size2.height) compare:@(size1.width*size1.height)];
  255. }];
  256. __block CGFloat maxWidth = 0;
  257. __block CGFloat maxHeight = 0;
  258. VapxBox *rootBox = [VapxBox new];
  259. rootBox.rect = NSZeroRect;
  260. BOOL rightFirst = YES;
  261. //宽型视频
  262. if (videoSize.width >= videoSize.height) {
  263. maxWidth = [sortedSizes[0] sizeValue].width;
  264. rootBox.rect = NSMakeRect(0, 0, maxWidth, 10000);
  265. } else {
  266. //长条形视频
  267. maxHeight = [sortedSizes[0] sizeValue].height;
  268. rightFirst = NO;
  269. rootBox.rect = NSMakeRect(0, 0, 10000, maxHeight);
  270. }
  271. [sortedSizes enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
  272. NSSize size = [obj sizeValue];
  273. VapxBox *box = [self findBox:rootBox size:size rightFirst:rightFirst];
  274. if (box) {
  275. [self splitBox:box size:size];
  276. maxWidth = MAX(maxWidth, box.rect.origin.x+size.width);
  277. maxHeight = MAX(maxHeight, box.rect.origin.y+size.height);
  278. }
  279. }];
  280. return NSMakeSize(maxWidth, maxHeight);
  281. }
  282. - (VapxBox *)findBox:(VapxBox *)root size:(NSSize)size rightFirst:(BOOL)rightFirst {
  283. if (root.used) {
  284. if (rightFirst) {
  285. VapxBox *rightBox = [self findBox:root.right size:size rightFirst:rightFirst];
  286. if (rightBox) {
  287. return rightBox;
  288. }
  289. return [self findBox:root.down size:size rightFirst:rightFirst];
  290. } else {
  291. VapxBox *downBox = [self findBox:root.down size:size rightFirst:rightFirst];
  292. if (downBox) {
  293. return downBox;
  294. }
  295. return [self findBox:root.right size:size rightFirst:rightFirst];
  296. }
  297. } else if (size.width <= root.rect.size.width && size.height <= root.rect.size.height) {
  298. return root;
  299. }
  300. return nil;
  301. }
  302. - (VapxBox *)splitBox:(VapxBox *)box size:(NSSize)size {
  303. box.used = YES;
  304. VapxBox *downBox = [VapxBox new];
  305. downBox.rect = NSMakeRect(box.rect.origin.x, box.rect.origin.y+size.height, box.rect.size.width, box.rect.size.height-size.height);
  306. box.down = downBox;
  307. VapxBox *rightBox = [VapxBox new];
  308. rightBox.rect = NSMakeRect(box.rect.origin.x+size.width, box.rect.origin.y, box.rect.size.width-size.width, size.height);
  309. box.right = rightBox;
  310. return box;
  311. }
  312. @end