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]