iOS视频添加水印遇到的坑

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

Leave a Reply

Required fields are marked *