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;
}

