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 ...
随机推荐
- 用python批量下载贴吧图片 附源代码
环境:windows 7 64位:python2.7:IDE pycharm2016.1 功能: 批量下载百度贴吧某吧某页的所有帖子中的所有图片 使用方法: 1.安装python2.7,安装re模块, ...
- c#中dynamic ExpandoObject的用法
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; usin ...
- [python][django学习篇][9]设计正在博客视图(3)
需求: 真正的首页视图函数,当用户访问我们的博客首页时,他将看到我们发表的博客文章列表,就像 演示项目 里展示的这样.t https://docs.djangoproject.com/en/1.10/ ...
- Tomcat源码分析(二)------ 一次完整请求的里里外外
Tomcat源码分析(二)------ 一次完整请求的里里外外 前几天分析了一下Tomcat的架构和启动过程,今天开始研究它的运转机制.Tomcat最本质就是个能运行JSP/Servlet的Web ...
- 【bzoj4059】[Cerc2012]Non-boring sequences 分治
题目描述 我们害怕把这道题题面搞得太无聊了,所以我们决定让这题超短.一个序列被称为是不无聊的,仅当它的每个连续子序列存在一个独一无二的数字,即每个子序列里至少存在一个数字只出现一次.给定一个整数序列, ...
- Codeforces Round #316 (Div. 2) C 思路/模拟
C. Replacement time limit per test 2 seconds memory limit per test 256 megabytes input standard inpu ...
- bzoj 2503 相框 分类讨论
题目大意:给定一张无向图,每次可以进行以下两种操作: 1.将一个点分裂成一些点,原先这个点连接的每条边任选一个新点进行连接 2.将两个度数为1的点合并为1个点 求将这个图变成一个环的最小操作次数 我们 ...
- 沼泽鳄鱼(bzoj 1898)
Description 潘塔纳尔沼泽地号称世界上最大的一块湿地,它地位于巴西中部马托格罗索州的南部地区.每当雨季来临,这里碧波荡漾.生机盎然,引来不少游客.为了让游玩更有情趣,人们在池塘的中央建设了几 ...
- .net3.5下使用LINQ递归算法实现简洁代码
原文发布时间为:2011-04-24 -- 来源于本人的百度文章 [由搬家工具导入] http://www.cnblogs.com/wintersun/archive/2009/03/29/14243 ...
- SQL触发器的使用及语法
原文发布时间为:2010-08-07 -- 来源于本人的百度文章 [由搬家工具导入] ===以下转qsfwy.javaeye.com/blog/424789定义: 何为触发器?在SQL Server里 ...