The beginning of the nonsense: passopenCVPixel point modification to achieve a single image withgifPictures are put together to show the effect.

1. Effect display

Combine an ancient beast 犼 and fire animation to display.

The effect of the merger

There’s no saving going on here, there’s just drawing going on, and this is a place where you can optimize.

Second, implementation ideas

1. Tiling 犼 pictures.

2, split the graph group of fire graph, calculate the display time of each graph.

3. Timer cycle for picture filling.

Three, code implementation

Start by creating a WSLLetMeSmile class that implements all the logic internally.

1. External calls
NSString * bundleImage = [[NSBundle mainBundle] pathForResource:@"犼" ofType:@"jpeg"]; UIImage * image = [UIImage imageWithContentsOfFile:bundleImage]; UIImageView * imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.width * image.size.height / image.size.width)]; imageView.center = self.view.center; [self.view addSubview:imageView]; NSURL *url = [[NSBundle mainBundle] URLForResource:@" fire "withExtension:@" GIF "]; / / convert image source CGImageSourceRef gifImageSourceRef = CGImageSourceCreateWithURL (url (CFURLRef), nil); / / main thread and render the self. LetMeSmile = [WSLLetMeSmile letMeSmileWithImage: image gifSource: gifImageSourceRef resImage: ^ (UIImage * _Nonnull resImage) { dispatch_async(dispatch_get_main_queue(), ^{ imageView.image = resImage; }); }];Copy the code
2, WSLLetMeSmile class implementation

WSLLetMeSmile.h

Contains only one class method, and the parameters are a base image, A GIF group, and a render callback closure

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>

NS_ASSUME_NONNULL_BEGIN

@interface WSLLetMeSmile : NSObject
+ (WSLLetMeSmile *)letMeSmileWithImage:(UIImage *)image gifSource:(CGImageSourceRef)gifSource resImage:(void(^)(UIImage *))resCallBack;

@end

NS_ASSUME_NONNULL_END

Copy the code

WSLLetMeSmile.m

GIF information acquisition, rendering merging and pixel threshold judgment of related images are required (removing some incongruous factors in THE GIF).

#ifdef __cplusplus
#import <opencv2/opencv.hpp>
#import <opencv2/imgcodecs/ios.h> // Mat 和 UIImage互转
#endif

#import "WSLLetMeSmile.h"

//命名空间
using namespace cv;

//渲染回调闭包
typedef void(^ResCallBack)(UIImage *);

@interface WSLLetMeSmile()
//定时器
@property (nonatomic,strong) dispatch_source_t timer;
//回调闭包
@property (nonatomic,copy) ResCallBack resCallBack;
//gif 图时间集合
@property (nonatomic,strong) NSMutableArray * gifTimeArr;

@end

@implementation WSLLetMeSmile

- (instancetype)init
{
    self = [super init];
    if (self) {
        self.gifTimeArr = [[NSMutableArray alloc] init];
    }
    return self;
}

/初始化
+ (WSLLetMeSmile *)letMeSmileWithImage:(UIImage *)image gifSource:(CGImageSourceRef)gifSource resImage:(void(^)(UIImage *))resCallBack
{
    WSLLetMeSmile * letMeSmile = [[WSLLetMeSmile alloc] init];
    letMeSmile.resCallBack = resCallBack;
    [letMeSmile addAnimationWithImage:image gifSource:gifSource];
    return letMeSmile;
}

//添加动图
- (void)addAnimationWithImage:(UIImage *)image gifSource:(CGImageSourceRef)gifSource
{
    __weak typeof(self) weakSelf = self;
    [self getGifDurationWithGifSource:gifSource callBack:^(NSTimeInterval totalDuration, NSArray *frames) {
        
        //播放gif图组索引
        __block int index = 0;
        //循环时间
        __block float time = 0;
        //gif下一张图片展示时间
        __block float nextTime = 0;
        //定时器初始化
        dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
        weakSelf.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
        dispatch_source_set_timer(weakSelf.timer, dispatch_walltime(NULL, 0), 0.01*NSEC_PER_SEC,  0); 
        dispatch_source_set_event_handler(weakSelf.timer, ^{
        
            __strong typeof(weakSelf) strongSelf = weakSelf;
            //循环一个周期,重新开始
            if (time > totalDuration) {
                time = 0;
                index = 0;
                //犼图转换
                Mat _other;
                UIImageToMat(image, _other);
                //gif图转换
                Mat _fire;
                UIImageToMat(frames.firstObject, _fire);
                Mat result = [strongSelf drawOnFace:_other fire:_fire];
                UIImage * smileImage = MatToUIImage(result);
                weakSelf.resCallBack(smileImage);
                nextTime = [strongSelf.gifTimeArr[index + 1] floatValue];
            }
            
            //时间到下一张节点后渲染下一张图片
            if (time >= nextTime) {
                index += 1;
                if (index < frames.count) {
                    //犼图转换
                    Mat _other;
                    UIImageToMat(image, _other);
                    //gif图转换
                    Mat _fire;
                    UIImageToMat(frames[index], _fire);
                    Mat result = [strongSelf drawOnFace:_other fire:_fire];
                    UIImage * smileImage = MatToUIImage(result);
                    strongSelf.resCallBack(smileImage);
                }
                if (index + 1 < strongSelf.gifTimeArr.count) {
                    nextTime = [strongSelf.gifTimeArr[index + 1] floatValue];
                }
            }
            time += 0.01;
        });
        dispatch_resume(weakSelf.timer);
    }];
}

//合并渲染
- (Mat)drawOnFace:(Mat &)img fire:(Mat)fire
{
    //底图尺寸
    int otherWidth = img.cols;
    int otherHeight = img.rows;
    //gif单张图尺寸
    int fireWidth = fire.cols;
    int fireHeight = fire.rows;
    
    //这里需要转换,否则定位不准
    Mat showImg(cvRound(img.rows), cvRound(img.cols), CV_8UC1 );
    cvtColor(img, showImg, COLOR_BGR2RGB);

    Mat showFireImg(cvRound(img.rows), cvRound(img.cols), CV_8UC1 );
    cvtColor(fire, showFireImg, COLOR_BGR2RGB);
    
    //x轴剧中绘制
    int addX = (otherWidth - fireWidth) / 2;
    //y轴底部对齐
    int addY = otherHeight - fireHeight;
    for (int x = 0; x < fireWidth; x ++) {
        for (int y = 0; y < fireHeight; y++) {
        
            int b = showFireImg.at<Vec3b>(y,x)[0];
            int g = showFireImg.at<Vec3b>(y,x)[1];
            int r = showFireImg.at<Vec3b>(y,x)[2];
            //设置阀值,去掉偏黑色的影响因素
            int threshold = 5;
            if (r < threshold || g < threshold || b < threshold) {
                continue;
            }
            
            //底图进行绘制动图的像素值
            showImg.at<Vec3b>(y + addY,x + addX)[0] = b;
            showImg.at<Vec3b>(y + addY,x + addX)[1] = g;
            showImg.at<Vec3b>(y + addY,x + addX)[2] = r;
        }

    }
    Mat mat_image_face;
    cvtColor(showImg, mat_image_face, cv::COLOR_BGR2RGB, 3);
    return mat_image_face;
}

//gif动图时间处理
- (void)getGifDurationWithGifSource:(CGImageSourceRef)gifSource callBack:(void(^)(NSTimeInterval totalDuration,NSArray * frames))callBack
{
    [self.gifTimeArr removeAllObjects];
    size_t frameCount = CGImageSourceGetCount(gifSource);
    NSMutableArray * saveFrames = [[NSMutableArray alloc] init];
    NSTimeInterval totalDuration = 0;
    //记录开始时间
    [self.gifTimeArr addObject:[NSNumber numberWithDouble:totalDuration]];
    
    for (size_t i = 0; i < frameCount; i++) 
        CGImageRef imageRef = CGImageSourceCreateImageAtIndex(gifSource, i, NULL);
        UIImage * image = [UIImage imageWithCGImage:imageRef];
        [saveFrames addObject:image];
        //获取每帧时间
        NSTimeInterval duration = [self getGifPerFrameTime:gifSource index:i];
        totalDuration += duration;
        [self.gifTimeArr addObject:[NSNumber numberWithDouble:totalDuration]];
        CGImageRelease(imageRef);
    }

    CFRelease(gifSource);
    callBack(totalDuration,saveFrames);

}

//获取每帧时间
- (NSTimeInterval)getGifPerFrameTime:(CGImageSourceRef)gifSource index:(NSInteger)index
{
    NSTimeInterval duration = 0;
    CFDictionaryRef frameProperties = CGImageSourceCopyPropertiesAtIndex(gifSource, index, NULL);
    
    if (frameProperties) {
        CFDictionaryRef gifProperties;
        BOOL result = CFDictionaryGetValueIfPresent(frameProperties, kCGImagePropertyGIFDictionary, (const void **)&gifProperties);
        if (result) {
            const void *durationValue;
            if (CFDictionaryGetValueIfPresent(gifProperties,kCGImagePropertyGIFUnclampedDelayTime,&durationValue)) {

                duration = [( __bridge NSNumber *)durationValue doubleValue];
                if (duration < 0) {
                    if (CFDictionaryGetValueIfPresent(gifProperties, kCGImagePropertyGIFDelayTime, &durationValue)) {

                                            duration = [( __bridge NSNumber *)durationValue doubleValue];
                     }
                }
            }
        }
    }
    return duration;
}

-(void)dealloc
{
    NSLog(@"我销毁了");
    //销毁定时器
    dispatch_source_cancel(self.timer);
}

@end

Copy the code

Fourth, summary and thinking

The simple merge presentation function is done.

Note that:

1, before conversion, the image needs to be cvtColor processing, otherwise pixel positioning is not accurate.

2. When the coordinate pixel value is modified, the first parameter in at< Vec3b > is y value and the second parameter is x value. If CV ::Point is selected as the parameter, parameter confusion can be avoided.

No advanced content, god do not laugh, common progress. [fist][fist][Fist]