iOS视频添加水印,水印须有动画,还能支持多图片动画,也就是gif。
1,如水印需要移动,捏合,旋转,则最好用frame去管理,不要用autoLayout;
2,最终合成的水印是添加在layer上边的,不管是动画还是内容,内容则是layer.contents;
3,水印的显示时间和时机都是由动画的行为时间去控制,例如在视频开始显示3秒钟的水印,并且水印出现和消失的时候需要动画,则要通过动画去消失或者显示,并且控制其时间;
4,视频中layer合成的动画显示时间和view中当前显示的动画时间不一致。
//根据动画展示父类不同返回时间 - (CFTimeInterval)initCFTimeDisplayWay:(StickerDisplayWay)way { return way == StickerDisplay_Show ? CACurrentMediaTime() : AVCoreAnimationBeginTimeAtZero; }
5,gif图添加播放的时候,必须释放cgimage资源。
CFRelease(cImageSource);
CGImageRelease(cgimage);
最好再这里返回的代码块增加自动释放池,这里没有加。
//web url with gif + (CAKeyframeAnimation *)gifWithURL:(NSString *)url duration:(float)duration repeatCount:(float)repeatCount beginTime:(CFTimeInterval)beginTime atComplateRemove:(BOOL)remove { CGImageSourceRef cImageSource = [self CGImageSourceCreateWithURL:url]; // size_t imageCount = CGImageSourceGetCount(cImageSource); NSMutableArray *images = [[NSMutableArray alloc] initWithCapacity:imageCount]; NSMutableArray *times = [[NSMutableArray alloc] initWithCapacity:imageCount]; NSMutableArray *keyTimes = [[NSMutableArray alloc] initWithCapacity:imageCount]; CGFloat totalTime = 0; for (size_t i = 0; i < imageCount; i++) { CGImageRef cgimage= CGImageSourceCreateImageAtIndex(cImageSource, i, NULL); [images addObject:(__bridge id)cgimage]; CGImageRelease(cgimage); NSDictionary *properties = (__bridge NSDictionary *)CGImageSourceCopyPropertiesAtIndex(cImageSource, i, NULL); NSDictionary *gifProperties = [properties valueForKey:(__bridge NSString *)kCGImagePropertyGIFDictionary]; NSString *gifDelayTime = [gifProperties valueForKey:(__bridge NSString* )kCGImagePropertyGIFDelayTime]; [times addObject:gifDelayTime]; totalTime += [gifDelayTime floatValue]; } CFRelease(cImageSource); float currentTime = 0; for (size_t i = 0; i < times.count; i++) { float keyTime = currentTime / totalTime; [keyTimes addObject:[NSNumber numberWithFloat:keyTime]]; currentTime += [[times objectAtIndex:i] floatValue]; } CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:StickerAnimationByContents]; [animation setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]]; [animation setValues:images]; [animation setKeyTimes:keyTimes]; animation.beginTime = beginTime; animation.removedOnCompletion = remove; animation.fillMode = kCAFillModeForwards; animation.duration = duration; //totalTime; animation.repeatCount = repeatCount; return animation; }
//然后再播放完成的delegate中移除动画: - (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag { if([self isKindOfClass:NSClassFromString(@"StickerImageGifView")]){ CAKeyframeAnimation *gifAnimation = (CAKeyframeAnimation *)anim; if([gifAnimation.keyPath rangeOfString:StickerAnimationByContents].location != NSNotFound){ __weak typeof (self)selfWeak = self; [self.layer.animationKeys enumerateObjectsUsingBlock:^(NSString * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { if([obj rangeOfString:@"Gif"].location != NSNotFound){ [selfWeak removeAnimationForKey:obj]; } }]; } } }
6,如果layer有捏合,旋转等功能。则动画会印象layer上层view的捏合,旋转,其测试会影响的动画有Transform,bounds,如需两者兼得,则在layer动画结束以后,移除当前layer的动画,然后可以对layer对应的view进行捏合,旋转等操作。
#pragma mark CAAnimation delegate - (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag { if(flag == YES){ CABasicAnimation *baseAnimation = (CABasicAnimation *)anim; if([baseAnimation.keyPath rangeOfString:StickerAnimationByTransform].location != NSNotFound){ __weak typeof (self)selfWeak = self; [self.layer.animationKeys enumerateObjectsUsingBlock:^(NSString * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { if([obj rangeOfString:@"Transform"].location != NSNotFound){ //移除动画 [selfWeak removeAnimationForKey:obj]; } } } }
7,贴纸View生成image时通过父类view截取屏幕,因为捏合/旋转的时候view的frame没有改变,需改变frame或者通过父类截屏,且父类view是透明的。
// //@interface UIImage (screen) - (UIImage *)screenshotWithRect:(CGRect)rect optionScale:(CGFloat)scale { CGFloat optionScale = scale <= 0 ? [UIScreen mainScreen].scale : scale; UIGraphicsBeginImageContextWithOptions(rect.size, NO, optionScale); CGContextRef context = UIGraphicsGetCurrentContext(); if (context == NULL) { return nil; } CGContextSaveGState(context); CGContextTranslateCTM(context, -rect.origin.x, -rect.origin.y); /* if( [self respondsToSelector:@selector(drawViewHierarchyInRect:afterScreenUpdates:)]) { CGRect screenRect = CGRectMake(0, 0, rect.size.width, rect.size.height); //截view [self drawViewHierarchyInRect:screenRect afterScreenUpdates:NO]; }else { //截view.layer [self.layer renderInContext:context]; } */ [self.layer renderInContext:context]; CGContextRestoreGState(context); UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); return image; }
8,每次给贴纸赋值title或者text的时候,如果当前贴纸有捏合或者旋转了,需将view的transform恢复原始,view.transform = CGAffineTransformIdentity;
9,gif旋转之后,要修改layer的transform,首先获取展示贴纸的transform,然后获取弧度,修改layer的transform,layer中transform的角度修改和view中transform是相反的。
// //gif最后生成的合成的layer改变transform - (void)finalStickerLayerAdjustTransform { NSLog(@"self.transform - - - %@",NSStringFromCGAffineTransform(self.transform)); if([self isKindOfClass:NSClassFromString(@"StickerImageGifView")]){ CGAffineTransform t = self.transform; //通过view的旋转获取最终的弧度 CGFloat rotate = atan2f(t.b, t.a); if(rotate == 0) return; CGRect sourceRect = self.videoStickerLayer.frame; self.videoStickerLayer.contentsGravity = kCAGravityResizeAspectFill; self.videoStickerLayer.transform = CATransform3DMakeRotation(-rotate, 0, 0, 1); CGFloat scale = sourceRect.size.width / self.videoStickerLayer.frame.size.width; self.videoStickerLayer.transform = CATransform3DScale(self.videoStickerLayer.transform , scale, scale, 1); } }
10,视频最终合成的时候,需要判断当前视频的方向,否则会对1024的视频进行90度旋转。
//======视频方向 // UIImageOrientation videoAssetOrientation_ = UIImageOrientationUp; BOOL isVideoAssetPortrait_ = NO; CGAffineTransform videoTransform = videoAssetTrack.preferredTransform; CGAffineTransform translateToCenter=CGAffineTransformMakeTranslation(0.0,0.0); int degrees=0; if (videoTransform.a == 0 && videoTransform.b == 1.0 && videoTransform.c == -1.0 && videoTransform.d == 0) { //90 degrees=90; // videoAssetOrientation_ = UIImageOrientationRight; isVideoAssetPortrait_ = YES; translateToCenter = CGAffineTransformMakeTranslation(videoAssetTrack.naturalSize.height,0.0); } if (videoTransform.a == 0 && videoTransform.b == -1.0 && videoTransform.c == 1.0 && videoTransform.d == 0) { // 270 degrees=270; // videoAssetOrientation_ = UIImageOrientationLeft; isVideoAssetPortrait_ = YES; translateToCenter = CGAffineTransformMakeTranslation(0.0, videoAssetTrack.naturalSize.width); } if (videoTransform.a == 1.0 && videoTransform.b == 0 && videoTransform.c == 0 && videoTransform.d == 1.0) { // 0 degrees=0; // videoAssetOrientation_ = UIImageOrientationUp; } if (videoTransform.a == -1.0 && videoTransform.b == 0 && videoTransform.c == 0 && videoTransform.d == -1.0) { // 180 degrees=180; // videoAssetOrientation_ = UIImageOrientationDown; translateToCenter = CGAffineTransformMakeTranslation(videoAssetTrack.naturalSize.width, videoAssetTrack.naturalSize.height); } CGAffineTransform mixedTransform = CGAffineTransformRotate(translateToCenter,degrees/180.0*M_PI); [videolayerInstruction setTransform:mixedTransform atTime:kCMTimeZero];
11,最终合成的时候需要视频的宽度除以当前视频播放layer的宽度scale,重新给贴纸layer计算frame。
-(CGFloat)stickerScale { return self.video.naturalSize.width/self.videoPlayer.width; }