IM即时通讯群组头像拼接.net core 解决方案
一、需求概述
多人聊天(群组,讨论组,聊天室,以下统称: “群组” )生成一个拼接头像,需要把最先加入群组的几个人(最多4个人,以下简称:头部用户,A、B、C、D)的头像拼凑成在一起。
群组创建后,A、B、C、D其中任何一个修改了自己的头像,需要 "异步" 更新群组头像。
以上是简单的需求描述。
本文使用.net core实现了N张图片拼接算法。
完整代码点击:https://github.com/night-king/ImageMerge
二、方案实现探讨
本需求可以在服务端实现,也可以在客户端实现。
(1)服务端实现:
群组创建时,下载A、B、C、D的头像,然后合并即可;
头部用户修改头像后,后台任务刷新群组头像。
(2)客户端实现:客户端包括iOS,Android,React,Web。
情况有些复杂,所以最后决定直接采用服务端实现。
Github上搜索无果,只能自己动手完成了。
先看效果:
采用四位Github大神头像作为源图片:
结果如下:
三、解决方案
群组2个用户,3个用户,4个以及以上用户头像不一样,总结如下:
/// <summary>
/// 合并布局枚举
/// </summary>
public enum Merge2LayoutEnum
{
/// <summary>
/// 2张图片,上下各1个长方形
/// ———————————————————
/// | |
/// | R1 |
/// | |
/// ———————————————————
/// | |
/// | R2 |
/// | |
/// ———————————————————
/// </summary>
Merge2R1 = , /// <summary>
/// 2张图片,左右各1个长方形
/// ———————————————————
/// | | |
/// | | |
/// | | |
/// | R1 | R2 |
/// | | |
/// | | |
/// | | |
/// ———————————————————
/// </summary>
Merge2R2 =
} public enum Merge3LayoutEnum
{ /// <summary>
/// 3张图片, 上面一个长方形,下面2个正方形并排
/// ———————————————————
/// | |
/// | R1 |
/// | |
/// ———————————————————
/// | | |
/// | S1 | S2 |
/// | | |
/// ———————————————————
/// </summary>
Merge1R2S1 = , /// <summary>
/// 3张图片,上面2个正方形并排,下面一个长方形
/// ———————————————————
/// | | |
/// | S1 | S2 |
/// | | |
/// ———————————————————
/// | |
/// | R1 |
/// | |
/// ———————————————————
/// </summary>
Merge1R2S2 = , /// <summary>
/// 3张图片,上面2个正方形并排,下面一个长方形
/// ———————————————————
/// | | |
/// | | S1 |
/// | | |
/// | R1 |—————————
/// | | |
/// | | S2 |
/// | | |
/// ———————————————————
/// </summary>
Merge1R2S3 = , /// <summary>
/// 3张图片,上面2个正方形并排,下面一个长方形
/// ———————————————————
/// | | |
/// | S2 | |
/// | | |
/// |—————————| R1 |
/// | | |
/// | S2 | |
/// | | |
/// ———————————————————
/// </summary>
Merge1R2S4 =
} public enum Merge4LayoutEnum
{
/// <summary>
/// 4张图片,上面一个长方形,下面2个正方形并排
/// ———————————————————
/// | | |
/// | S1 | S2 |
/// | | |
/// ———————————————————
/// | | |
/// | S3 | S4 |
/// | | |
/// ———————————————————
/// </summary>
Merge4S = }
以下是2张图片实现代码:
/// <summary>
/// 合并2张图片
/// </summary>
/// <param name="image1"></param>
/// <param name="image2"></param>
/// <param name="layout"></param>
/// <returns></returns>
public static Image Merge2Images(Image image1, Image image2, Merge2LayoutEnum layout = Merge2LayoutEnum.Merge2R1, int size = )
{
var width = size;
var height = size;
var pf = PixelFormat.Format32bppArgb;
using (var bg = new Bitmap(width, height, pf))
{
using (var g = Graphics.FromImage(bg))
{
g.FillRectangle((Brush)Brushes.White, , , width, height);//全幅背景为白色
g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
switch (layout)
{
/// <summary>
/// 2张图片,上下各1个长方形
/// ———————————————————
/// | |
/// | R1 |
/// | |
/// ———————————————————
/// | |
/// | R2 |
/// | |
/// ———————————————————
/// </summary>
case Merge2LayoutEnum.Merge2R1:
{
using (var img1 = ZoomToSqure(image1, size))
{
var newHeight = height / ;// =125
var newWidth = width;// =250
var srcWidth = img1.Width;// =250
var srcHeight = img1.Height * newHeight / newWidth;//=250*125/250=125
g.DrawImage(img1, new Rectangle(, , newWidth, newHeight), new Rectangle(, , srcWidth, srcHeight), GraphicsUnit.Pixel);
}
using (var img2 = ZoomToSqure(image2, size))
{
var newHeight = height / ;// =125
var newWidth = width; // =250
var srcWidth = img2.Width;// =250
var srcHeight = img2.Height * newHeight / newWidth;//=250*125/250=125
g.DrawImage(img2, new Rectangle(, height / , newWidth, newHeight), new Rectangle(, , srcWidth, srcHeight), GraphicsUnit.Pixel);
}
}
break;
/// <summary>
/// 2张图片,左右各1个长方形
/// ———————————————————
/// | | |
/// | | |
/// | | |
/// | R1 | R2 |
/// | | |
/// | | |
/// | | |
/// ———————————————————
/// </summary>
case Merge2LayoutEnum.Merge2R2:
{
using (var img1 = ZoomToSqure(image1, size))
{
var newWidth = width / ;// =125
var newHeight = height; // =250
var srcWidth = img1.Width * newWidth / newHeight;//=250*125/250=125
var srcHeight = img1.Height;// =250
g.DrawImage(img1, new Rectangle(, , newWidth, newHeight), new Rectangle(, , srcWidth, srcHeight), GraphicsUnit.Pixel);
}
using (var img2 = ZoomToSqure(image2, size))
{
var newWidth = width / ;// =125
var newHeight = height; // =250
var srcWidth = img2.Width * newWidth / newHeight;//=500*125/250=250
var srcHeight = img2.Height;// =500
g.DrawImage(img2, new Rectangle(width / , , newWidth, newHeight), new Rectangle(, , srcWidth, srcHeight), GraphicsUnit.Pixel);
}
}
break;
}
g.Save();
image1.Dispose();
image2.Dispose();
} using (var ms = new MemoryStream())
{
bg.Save(ms, ImageFormat.Png);
var buffers = ms.ToArray();
return ConvertToImage(buffers);
}
}
}
以下是三种图片的生成算法
/// <summary>
/// 合并3张图片
/// </summary>
/// <param name="image1"></param>
/// <param name="image2"></param>
/// <param name="image3"></param>
/// <param name="layout"></param>
/// <returns></returns>
public static Image Merge3Images(Image image1, Image image2, Image image3, Merge3LayoutEnum layout = Merge3LayoutEnum.Merge1R2S1, int size = )
{
var width = size;
var height = size;
var pf = PixelFormat.Format32bppArgb;
using (var bg = new Bitmap(width, height, pf))
{
using (var g = Graphics.FromImage(bg))
{
g.FillRectangle((Brush)Brushes.White, , , width, height);//全幅背景为白色
switch (layout)
{
/// <summary>
/// 3张图片, 上面一个长方形,下面2个正方形并排
/// ———————————————————
/// | |
/// | R1 |
/// | |
/// ———————————————————
/// | | |
/// | S1 | S2 |
/// | | |
/// ———————————————————
/// </summary>
case Merge3LayoutEnum.Merge1R2S1:
{
using (var img1 = ZoomToSqure(image1, size))
{
var newHeight = height / ;// =125
var newWidth = width;// =250
var srcWidth = img1.Width;// =250
var srcHeight = img1.Height * newHeight / newWidth;//=250*125/250=125
g.DrawImage(img1, new Rectangle(, , newWidth, newHeight), new Rectangle(, , srcWidth, srcHeight), GraphicsUnit.Pixel);
}
using (var img2 = ZoomToSqure(image2, size))
{
var newHeight = height / ;// =125
var newWidth = width / ; // =125
var srcWidth = img2.Width; // =250
var srcHeight = img2.Height;// =250
g.DrawImage(img2, new Rectangle(, height / , newWidth, newHeight), new Rectangle(, , srcWidth, srcHeight), GraphicsUnit.Pixel);
}
using (var img3 = ZoomToSqure(image3, size))
{
var newHeight = height / ; // =125
var newWidth = width / ; // =125
var srcWidth = img3.Width; // =250
var srcHeight = img3.Height;// =250
g.DrawImage(img3, new Rectangle(width / , height / , newWidth, newHeight), new Rectangle(, , srcWidth, srcHeight), GraphicsUnit.Pixel);
}
}
break;
/// <summary>
/// 3张图片,上面2个正方形并排,下面一个长方形
/// ———————————————————
/// | | |
/// | S1 | S2 |
/// | | |
/// ———————————————————
/// | |
/// | R1 |
/// | |
/// ———————————————————
/// </summary>
case Merge3LayoutEnum.Merge1R2S2:
{
using (var img1 = ZoomToSqure(image1, size))
{
var newHeight = height / ;// =125
var newWidth = width / ; // =125
var srcWidth = img1.Width; // =250
var srcHeight = img1.Height;// =250
g.DrawImage(img1, new Rectangle(, , newWidth, newHeight), new Rectangle(, , srcWidth, srcHeight), GraphicsUnit.Pixel);
}
using (var img2 = ZoomToSqure(image2, size))
{
var newHeight = height / ; // =125
var newWidth = width / ; // =125
var srcWidth = img2.Width; // =250
var srcHeight = img2.Height;// =250
g.DrawImage(img2, new Rectangle(width / , , newWidth, newHeight), new Rectangle(, , srcWidth, srcHeight), GraphicsUnit.Pixel);
}
using (var img3 = ZoomToSqure(image3, size))
{
var newHeight = height / ;// =125
var newWidth = width;// =250
var srcWidth = img3.Width;// =250
var srcHeight = img3.Height * newHeight / newWidth;//=250*125/250=125
g.DrawImage(img3, new Rectangle(, height / , newWidth, newHeight), new Rectangle(, , srcWidth, srcHeight), GraphicsUnit.Pixel);
}
}
break;
/// <summary>
/// 3张图片,上面2个正方形并排,下面一个长方形
/// ———————————————————
/// | | |
/// | | S1 |
/// | | |
/// | R1 |—————————
/// | | |
/// | | S2 |
/// | | |
/// ———————————————————
/// </summary>
case Merge3LayoutEnum.Merge1R2S3:
{
using (var img1 = ZoomToSqure(image1, size))
{
var newHeight = height;// =250
var newWidth = width / ;// =125
var srcHeight = img1.Height;//=250
var srcWidth = img1.Width * newWidth / newHeight;// =250*125/250=125;
g.DrawImage(img1, new Rectangle(, , newWidth, newHeight), new Rectangle(, , srcWidth, srcHeight), GraphicsUnit.Pixel);
}
using (var img2 = ZoomToSqure(image2, size))
{
var newHeight = height / ;// =125
var newWidth = width / ; // =125
var srcWidth = img2.Width; // =250
var srcHeight = img2.Height;// = 250
g.DrawImage(img2, new Rectangle(width / , , newWidth, newHeight), new Rectangle(, , srcWidth, srcHeight), GraphicsUnit.Pixel);
}
using (var img3 = ZoomToSqure(image3, size))
{
var newHeight = height / ; // =125
var newWidth = width / ; // =125
var srcWidth = img3.Width; // =250
var srcHeight = img3.Height;// =250
g.DrawImage(img3, new Rectangle(width / , height / , newWidth, newHeight), new Rectangle(, , srcWidth, srcHeight), GraphicsUnit.Pixel);
}
}
break;
/// <summary>
/// 3张图片,上面2个正方形并排,下面一个长方形
/// ———————————————————
/// | | |
/// | S2 | |
/// | | |
/// |—————————| R1 |
/// | | |
/// | S2 | |
/// | | |
/// ———————————————————
/// </summary>
case Merge3LayoutEnum.Merge1R2S4:
{
using (var img1 = ZoomToSqure(image1, size))
{
var newHeight = height / ;// =125
var newWidth = width / ; // =125
var srcWidth = img1.Width; // =250
var srcHeight = img1.Height;// =250
g.DrawImage(img1, new Rectangle(, , newWidth, newHeight), new Rectangle(, , srcWidth, srcHeight), GraphicsUnit.Pixel);
}
using (var img2 = ZoomToSqure(image2, size))
{
var newHeight = height / ; // =125
var newWidth = width / ; // =125
var srcWidth = img2.Width; // =250
var srcHeight = img2.Height;// =250
g.DrawImage(img2, new Rectangle(, height / , newWidth, newHeight), new Rectangle(, , srcWidth, srcHeight), GraphicsUnit.Pixel);
}
using (var img3 = ZoomToSqure(image3, size))
{
var newHeight = height;// =250
var newWidth = width / ;// =125
var srcHeight = img3.Height;//=250
var srcWidth = img3.Width * newWidth / newHeight;// =250*125/250=125;
g.DrawImage(img3, new Rectangle(width / , , newWidth, newHeight), new Rectangle(, , srcWidth, srcHeight), GraphicsUnit.Pixel);
}
}
break;
}
g.Save();
image1.Dispose();
image2.Dispose();
image3.Dispose();
}
using (var ms = new MemoryStream())
{
bg.Save(ms, ImageFormat.Png);
var buffers = ms.ToArray();
return ConvertToImage(buffers);
}
}
}
以下是4张图片的生成是算法:
/// <summary>
/// 合并4张图片
/// </summary>
/// <param name="image1"></param>
/// <param name="image2"></param>
/// <param name="image3"></param>
/// <param name="image4"></param>
/// <param name="layout"></param>
/// <returns></returns>
public static Image Merge4Images(Image image1, Image image2, Image image3, Image image4, Merge4LayoutEnum layout = Merge4LayoutEnum.Merge4S, int size = )
{
var width = size;
var height = size;
var pf = PixelFormat.Format32bppArgb;
using (var bg = new Bitmap(width, height, pf))
{
using (var g = Graphics.FromImage(bg))
{
g.FillRectangle((Brush)Brushes.White, , , width, height);//全幅背景为白色
switch (layout)
{
/// <summary>
/// 4张图片,上面一个长方形,下面2个正方形并排
/// ———————————————————
/// | | |
/// | S1 | S2 |
/// | | |
/// ———————————————————
/// | | |
/// | S3 | S4 |
/// | | |
/// ———————————————————
/// </summary>
case Merge4LayoutEnum.Merge4S:
{
using (var img1 = ZoomToSqure(image1, size))
{
var newHeight = height / ;// =125
var newWidth = width / ; // =125
var srcWidth = img1.Width; // =250
var srcHeight = img1.Height;// =250
g.DrawImage(img1, new Rectangle(, , newWidth, newHeight), new Rectangle(, , srcWidth, srcHeight), GraphicsUnit.Pixel);
}
using (var img2 = ZoomToSqure(image2, size))
{
var newHeight = height / ; // =125
var newWidth = width / ; // =125
var srcWidth = img2.Width; // =250
var srcHeight = img2.Height;// =250
g.DrawImage(img2, new Rectangle(width / , , newWidth, newHeight), new Rectangle(, , srcWidth, srcHeight), GraphicsUnit.Pixel);
}
using (var img3 = ZoomToSqure(image3, size))
{
var newHeight = height / ; // =125
var newWidth = width / ; // =125
var srcWidth = img3.Width; // =250
var srcHeight = img3.Height;// =250
g.DrawImage(img3, new Rectangle(, height / , newWidth, newHeight), new Rectangle(, , srcWidth, srcHeight), GraphicsUnit.Pixel);
}
using (var img4 = ZoomToSqure(image4, size))
{
var newHeight = height / ; // =125
var newWidth = width / ; // =125
var srcWidth = img4.Width; // =250
var srcHeight = img4.Height;// =250
g.DrawImage(img4, new Rectangle(width / , height / , newWidth, newHeight), new Rectangle(, , srcWidth, srcHeight), GraphicsUnit.Pixel);
}
}
break;
}
g.Save();
image1.Dispose();
image2.Dispose();
image3.Dispose();
image4.Dispose();
}
using (var ms = new MemoryStream())
{
bg.Save(ms, ImageFormat.Png);
var buffers = ms.ToArray();
return ConvertToImage(buffers);
}
}
}
以下是用到的Helper方法:
/// <summary>
/// 下载图片
/// </summary>
/// <param name="imageUrl"></param>
/// <returns></returns>
public static byte[] Download(string imageUrl)
{
if (imageUrl.StartsWith("http"))
{
using (var ms = new MemoryStream())
{
var request = (HttpWebRequest)HttpWebRequest.Create(imageUrl);// 打开网络连接
using (var rs = request.GetResponse().GetResponseStream())// 向服务器请求,获得服务器的回应数据流
{
byte[] btArray = new byte[];// 定义一个字节数据,用来向readStream读取内容和向writeStream写入内容
int size = rs.Read(btArray, , btArray.Length);// 向远程文件读第一次 while (size > )// 如果读取长度大于零则继续读
{
ms.Write(btArray, , size);// 写入本地文件
size = rs.Read(btArray, , btArray.Length);// 继续向远程文件读取
}
return ms.ToArray();
}
}
}
else
{
using (var ms = new MemoryStream())
{
var img = Image.FromFile(imageUrl);
img.Save(ms, img.RawFormat);
return ms.ToArray();
}
}
} /// <summary>
/// 将byte数组转化为Image
/// </summary>
/// <param name="buffer"></param>
/// <returns></returns>
public static Image ConvertToImage(byte[] buffer)
{
using (MemoryStream ms = new MemoryStream(buffer))
{
return Image.FromStream(ms);
}
} /// <summary>
/// 将Image转化为byte数组
/// </summary>
/// <param name="image"></param>
/// <returns></returns>
public static byte[] ConvertToByte(Image image)
{
using (MemoryStream ms = new MemoryStream())
{
image.Save(ms, image.RawFormat);
return ms.ToArray();
}
} /// <summary>
/// 等比缩放/放大成正方形图片
/// </summary>
/// <param name="orginal">原始图片</param>
/// <param name="size">目标宽高</param>
/// <param name="cute">超出部分是否剪裁</param>
/// <returns></returns>
public static Image ZoomToSqure(Image orginal, int size, bool cute = true)
{
var width = 0d;//图片宽度
var height = 0d;//图片高度
if (orginal.Width > orginal.Height)//原始图片宽度大于高度
{
height = size;
width = cute ? size : (orginal.Width * height / orginal.Height);
}
else if (orginal.Width < orginal.Height)//原始图片高度大于宽度
{
width = size;
height = cute ? size : (orginal.Height * width / orginal.Width);
}
else//原始图片是正方形,刚好
{
width = size;
height = size;
}
var board = new Bitmap((int)width, (int)height);
using (var g = Graphics.FromImage(board))
{
g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;//设置质量
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;//设置质量
g.Clear(Color.White);//置背景色
g.DrawImage(orginal, new Rectangle(, , board.Width, board.Height), new Rectangle(, , orginal.Width, orginal.Height), System.Drawing.GraphicsUnit.Pixel); //画图
orginal.Dispose();//释放原图
return board;
}
}
完整代码点击:https://github.com/night-king/ImageMerge
IM即时通讯群组头像拼接.net core 解决方案的更多相关文章
- 一行实现QQ群组头像,微信群组,圆角等效果. 并支持url直接加载图片
说点题外话. Coding中我们总是经历着这么几个过程. 学会使用: 不管是API也好, 开源库也好. 总是在最开始的学会去用. 了解实现原理: 可能会因为一些不兼容, 代码的异常状态的处理不够完美等 ...
- layim即时通讯实例各功能整合
一.系统演示1.1 聊天窗体主界面演示 1.2 模拟两人在线聊天(点击图片查看演示视频) 1.3 在线演示> 在线演示,点击进入系统到这里,若是您想要的,接下来听我娓娓道来二.开发工具开发软件: ...
- Activiti6.0 工作流引擎 websocket即时聊天发图片文字 好友群组 SSM源码
即时通讯:支持好友,群组,发图片.文件,消息声音提醒,离线消息,保留聊天记录 (即时聊天功能支持手机端,详情下面有截图) 工作流模块---------------------------------- ...
- java工作流引擎 Activiti6.0 websocket 即时聊天发图片文字 好友群组 SSM源码
时通讯:支持好友,群组,发图片.文件,消息声音提醒,离线消息,保留聊天记录 工作流模块--------------------------------------------------------- ...
- java ssm 后台框架平台 项目源码 websocket即时聊天发图片文字 好友群组 SSM源码
官网 http://www.fhadmin.org/D 集成安全权限框架shiro Shiro 是一个用 Java 语言实现的框架,通过一个简单易用的 API 提供身份验证和授权,更安全,更可靠E ...
- mui初级入门教程(五)— 聊聊即时通讯(IM),基于环信 web im SDK
文章来源:小青年原创发布时间:2016-06-15关键词:mui,环信 web im,html5+,im,页面传值,缓存转载需标注本文原始地址: http://zhaomenghuan.github. ...
- iOS:融云即时通讯快速集成
一.介绍 即时通讯在众多社交软件.生活软件以及教育软件中已经是必备的功能了,在当前国内,即时通讯SDK做的比较不错的有那么几家,例如环信SDK.融云SDK...,这两家做的都很不错,各有千秋吧,要是真 ...
- GIM企业即时通讯
GIM企业即时通讯是笔者Garfield(QQ:3674571)采用.NetFramework4.0+SQL2008R2开发的一套企业内网/外网 通用的即时通讯(IM)软件,分为服务器端和客户端,通讯 ...
- apicloud+融云实现即时通讯
请尊重作者的辛勤劳动!!! 使用apicloud开发已经快2个月了,起初的目的就是为了实现安卓和苹果的兼容,属于一个试验项目,究竟apicloud是否能够满足公司的要求?最 终看来还是不错的,使用ap ...
随机推荐
- FreeMarker的基础语法使用 && 心得和技巧
FreeMarker语言 FreeMarker语言概述 FreeMarker是一个模板引擎,一个基于模板生成文本输出的通用工具,使用纯Java编写. FreeMarker被设计用来生成HTML Web ...
- OpenResty安装与hello world
安装环境:CentOS 7.0 1. 安装编译工具.依赖库 yum -y install readline-devel pcre-devel openssl-devel gcc 2. 下载openre ...
- laravel5.2总结--数据填充
1 生成一个seeder文件 你可以通过 make:seeder artisan命令来生成一个 Seeder.所有通过框架生成的 Seeder 都将被放置在 database/seeds 路径: ...
- 微信小程序-----校园头条详细开发之列表展示数据
1.分类列表数据展示功能的实现 1.1 结构 1.2 代码实现 1.2.1 列表显示数据,.每次界面显示6条数据,发请求获取数据,动态存放 var app = getApp() Page({ dat ...
- Wordpress 作者模板页中的自定义帖子类型分页问题
<?php // 获取当前页面的页数,函数的参数为 paged $paged = (get_query_var('paged')) ? get_query_var('paged') : 1; $ ...
- 基于单层决策树的AdaBoost算法源码
基于单层决策树的AdaBoost算法源码 Mian.py # -*- coding: utf-8 -*- # coding: UTF-8 import numpy as np from AdaBoos ...
- linux命令之grep、cut
输入: ifconfig eth0 eth0表示主机的第一块网卡. 输出: eth0: flags=<UP,BROADCAST,RUNNING,MULTICAST> mtu inet 19 ...
- JVM虚拟机系列(三)Class文件格式
- 整合SpringMVC与Mybatis
第一步.导包 第二步.配置springmvc springmvc.xml <?xml version="1.0" encoding="UTF-8"?> ...
- Log4j官方文档翻译(八、文件输出)
使用org.apache.log4j.FileAppender可以把日志写到文件中: FileAppender配置 immediateFlush 这个标志默认为true,是否每次有消息产生都自动flu ...