所谓水印是向数据多媒体(如图像、声音、视频信号等)中添加某些数字信息以达到文件真伪鉴别、版权保护等功能。嵌入的水印信息隐藏于宿主文件中,不影响原始文件的可观性和完整性。也就是在图片上打上半透明的标记。我们先来看一下Demo的运行效果图。转载请联系作者获取授权,并标明文章作者,谢谢!
核心技术:CoreGraphics绘图。闲话少说,上代码。
#define HORIZONTAL_SPACE 30//水平间距
#define VERTICAL_SPACE 50//竖直间距
#define CG_TRANSFORM_ROTATION (M_PI_2 / 3)//旋转角度(正旋45度 || 反旋45度)
/**
根据目标图片制作一个盖水印的图片
@param originalImage 源图片
@param title 水印文字
@param markFont 水印文字font(如果不传默认为23)
@param markColor 水印文字颜色(如果不传递默认为源图片的对比色)
@return 返回盖水印的图片
*/
+ (UIImage *)getWaterMarkImage: (UIImage *)originalImage andTitle: (NSString *)title andMarkFont: (UIFont *)markFont andMarkColor: (UIColor *)markColor{
UIFont *font = markFont;
if (font == nil) {
font = [UIFont systemFontOfSize:23];
}
UIColor *color = markColor;
if (color == nil) {
color = [self mostColor:originalImage];
}
//原始image的宽高
CGFloat viewWidth = originalImage.size.width;
CGFloat viewHeight = originalImage.size.height;
//为了防止图片失真,绘制区域宽高和原始图片宽高一样
UIGraphicsBeginImageContext(CGSizeMake(viewWidth, viewHeight));
//先将原始image绘制上
[originalImage drawInRect:CGRectMake(0, 0, viewWidth, viewHeight)];
//sqrtLength:原始image的对角线length。在水印旋转矩阵中只要矩阵的宽高是原始image的对角线长度,无论旋转多少度都不会有空白。
CGFloat sqrtLength = sqrt(viewWidth*viewWidth + viewHeight*viewHeight);
//文字的属性
NSDictionary *attr = @{
//设置字体大小
NSFontAttributeName: font,
//设置文字颜色
NSForegroundColorAttributeName :color,
};
NSString* mark = title;
NSMutableAttributedString *attrStr = [[NSMutableAttributedString alloc] initWithString:mark attributes:attr];
//绘制文字的宽高
CGFloat strWidth = attrStr.size.width;
CGFloat strHeight = attrStr.size.height;
//开始旋转上下文矩阵,绘制水印文字
CGContextRef context = UIGraphicsGetCurrentContext();
//将绘制原点(0,0)调整到源image的中心
CGContextConcatCTM(context, CGAffineTransformMakeTranslation(viewWidth/2, viewHeight/2));
//以绘制原点为中心旋转
CGContextConcatCTM(context, CGAffineTransformMakeRotation(CG_TRANSFORM_ROTATION));
//将绘制原点恢复初始值,保证当前context中心和源image的中心处在一个点(当前context已经旋转,所以绘制出的任何layer都是倾斜的)
CGContextConcatCTM(context, CGAffineTransformMakeTranslation(-viewWidth/2, -viewHeight/2));
//计算需要绘制的列数和行数
int horCount = sqrtLength / (strWidth + HORIZONTAL_SPACE) + 1;
int verCount = sqrtLength / (strHeight + VERTICAL_SPACE) + 1;
//此处计算出需要绘制水印文字的起始点,由于水印区域要大于图片区域所以起点在原有基础上移
CGFloat orignX = -(sqrtLength-viewWidth)/2;
CGFloat orignY = -(sqrtLength-viewHeight)/2;
//在每列绘制时X坐标叠加
CGFloat tempOrignX = orignX;
//在每行绘制时Y坐标叠加
CGFloat tempOrignY = orignY;
for (int i = 0; i < horCount * verCount; i++) {
[mark drawInRect:CGRectMake(tempOrignX, tempOrignY, strWidth, strHeight) withAttributes:attr];
if (i % horCount == 0 && i != 0) {
tempOrignX = orignX;
tempOrignY += (strHeight + VERTICAL_SPACE);
}else{
tempOrignX += (strWidth + HORIZONTAL_SPACE);
}
}
//根据上下文制作成图片
UIImage *finalImg = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
CGContextRestoreGState(context);
return finalImg;
}
下面我们讲一下原理
1.绘制源图片
拿到源图片,根据源图片尺寸调用UIGraphicsBeginImageContext方法将源图片绘制在画布上。
2.绘制水印文字:
要想绘制出倾斜任意角度的文字首先我们需要将画布倾斜,这样画出来的文字可以和没有倾斜的图片形成角度对比,给人感官上文字是倾斜的。
Demo中采用CGContextConcatCTM方法旋转画布,但是笔者查阅资料发现该方法并不是绕着中心点旋转,而是绕着绘制原点(也就是初始值0,0)旋转。所以转出来是这样的效果。
如果在这样的画布上绘制水印很明显不是我们想要的。所以我们先将绘制原点平移到源图片区域的中心点,然后旋转角度,最后在按照之前平移的X轴,Y轴数据反方向平移回去。这样就能保证旋转完画布的中心点和源图片的中心点在同一位置。
注意:在旋转过后当前画布的X轴,Y轴方向也跟着旋转,所以再次平移可以回到中心点。
这样我们得到了一个倾斜的画布开始绘制文字就可以了,还需注意如果我们还按照源图片大小的区域去绘制水印文字难免会有空白的地方(我们不知道水印要旋转多少度),所以我们要保证不出现空白,采用以源图片中心点,长宽为源图片对角线长度的绘制区域。这样无论旋转多少度都不会出现空白。如下图:
所以我们需要根据源图片的长宽计算出orignX和orignY(向上和向左偏移多少开始绘制)。同时根据水印文字的长宽,计算出整个绘制区域可以绘制出多少个水印,然后for循环绘制。
最后结束绘图调用UIGraphicsGetImageFromCurrentImageContext形成一个闭合区间的图片返回。
注意:代码中的mostColor方法作用是:根据图片获取一个对比色保证水印能清楚显示。参考地址:iOS代码获取图片主色调
总结
Core Graphics是一个古老的基于C的绘图专用API,也被称为QuartZ或QuartZ 2D,是一个二维绘图引擎。真如我们所知UIView底层都是覆盖着一层CALayer,而这个CALayer正是在/System/Library/Frameworks/QuartzCore.framework中定义,所以几乎所有的UI控件都是基于Core Graphics完成的。想要自定制UI组件Core Graphics是个很不错的选择。当然还有基于它封装的上层API UIBezierPath。
作者:Olivia_Zqy