ImageSharp一个专注于NetCore平台图像处理的开源项目
今天大家分享的是一个专注于NetCore平台图像处理的开源项目,老实说为这篇文章取名字想了5分钟,可能是词穷亦或是想更好的表达出这款开源项目的作用;这个项目在图像处理方面有很多功能,如:缩放,裁剪,绘画,组合图片等;今天主要讲的是用她怎么来绘图和生成验证码的实际例子。
- 简单介绍ImageSharp
- 试试画两条线(实线和虚线)
- 生成个缩略图
- 在图片上画字
- 制作一个验证码图片
- 结合RazorPage模板,展示验证码图片
简单介绍ImageSharp
ImageSharp是对NetCore平台扩展的一个图像处理方案,在写下本文为止它最新的nuget下载量为4,034次,作者团队最近一个月刚更新的包;没错这里说最新是因为她前身和之前的版本都很受欢迎下载量也超高;她的git项目地址:https://github.com/SixLabors/ImageSharp。如果您的项目和我一样是2.0版本(2.0以前的略过),那么直接可以通过vs的nuget控制台下载对应的包,注意绘图的话需要分别下载如下两个包:
Install-Package SixLabors.ImageSharp -Version 1.0.-beta0001
Install-Package SixLabors.ImageSharp.Drawing -Version 1.0.-beta0001
ImageSharp用法有朋友之前写过,不过都主要针对于之前的版本,本章主要用到的都是最新的,有部分写法可能不相同。
试试画两条线(实线和虚线)
这里将用她来画两条直线并保存成图片,主要起到一个介绍作用,先来看实线如下代码:
var path = @"D:\F\学习\vs2017\netcore\Study.AspNetCore\WebApp02-1\wwwroot\images";
//默认实线
using (Image<Rgba32> image = new Image<Rgba32>(, )) //画布大小
{
image.Mutate(x => x.
BackgroundColor(Rgba32.WhiteSmoke). //画布背景
DrawLines(
Rgba32.HotPink, //字体颜色
, //字体大小
new SixLabors.Primitives.PointF[]{
new Vector2(, ),
new Vector2(, ),
new Vector2(, )
} //两点一线坐标
)
); image.Save($"{path}/1.png"); //保存
}
总要步骤我都备注上文字了,这里主要通过两点一线来绘制图形,Vector2对象值得注意就是C#二维坐标(x,y)对象,其实除了Vector2还有Vector3(三维坐标)等,这对于做u3d的朋友来说不会陌生,老实说这个也是我在接触u3d时候才知道有这个类的。下面来看效果图:
由两个两点一线构造的一个角,下面来看下虚线绘制:
//虚线
using (Image<Rgba32> image = new Image<Rgba32>(, )) //画布大小
{
image.Mutate(x => x.
BackgroundColor(Rgba32.WhiteSmoke). //画布背景
DrawLines(
Pens.Dash(Rgba32.HotPink, ), //字体大小
new SixLabors.Primitives.PointF[]{
new Vector2(, ),
new Vector2(, ),
new Vector2(, )
} //两点一线坐标
)
); image.Save($"{path}/2.png"); //保存
}
步骤都差不多,只是调用了DrawLines的扩展方法而已,其他线条例子就不多说了各位自行实验。
生成个缩略图和在图片上画字
对于图片类型的网站来说缩略图是常见的,这里用ImageSharp生成缩略图很简单,本实例用8.png做样本来生成缩略图8-1.png,直接看例子如下是netstandard 1.3+的例子:
//缩略图
using (Image<Rgba32> image = Image.Load($"{path}/8.png"))
{
image.Mutate(x => x
.Resize(image.Width / , image.Height / )
);
image.Save($"{path}/8-1.png");
}
为了更好的对比缩略图和原图的区别这里对接拿两图的属性做对比如:
能很好的看出缩略图文件大小和像素都减半了,实际缩略的时候不一定减半,这全由参数控制Resize(width,height);
画字:在图片上画我们想要的字,其实类似于水印的一种需求,下面是在图片上画字的代码:
//画字
var install_Family = new FontCollection().Install(
System.IO.Path.Combine(Directory.GetCurrentDirectory(), "wwwroot/bak", "STKAITI.TTF")
//@"C:\Windows\Fonts\STKAITI.TTF" //字体文件
);
var font = new Font(install_Family, ); //字体
using (Image<Rgba32> image = Image.Load($"{path}/8.png"))
{
image.Mutate(x => x
.DrawText(
"你们好,我是神牛", //文字内容
font,
Rgba32.HotPink,
new Vector2(, ),
TextGraphicsOptions.Default)
);
image.Save($"{path}/8-2.png");
}
这里用ImageSharp在图片上画字的时候需要注意:字体,因为windows系统自带了字体问题这里以STKAITI.TTF字体文件为例,它存储于 C:\Windows\Fonts\STKAITI.TTF 目录,当然您可以直接把它拷贝到我们项目中如下我这里的例子一样做法(这里只测试了windows下可用,尚未测试linux下直接使用该字体文件是否可行);
制作一个验证码图片
下面我们将用她来画一个验证码类型的图片,通常验证码都有一些点和线来干扰,上面已经有画线例子了,这里展示怎么画点:
//画点(规则的点,其他的各位自行写算法)
var dianWith = ; //点宽度
var xx = ; //图片宽度
var yy = ; //图片高度 var xx_space = ; //点与点之间x坐标间隔
var yy_space = ; //y坐标间隔 var listPath = new List<IPath>();
for (int i = ; i < xx / xx_space; i++)
{
for (int j = ; j < yy / yy_space; j++)
{
var position = new Vector2(i * xx_space, j * yy_space);
var linerLine = new LinearLineSegment(position, position);
var shapesPath = new SixLabors.Shapes.Path(linerLine);
listPath.Add(shapesPath);
}
} using (Image<Rgba32> image = new Image<Rgba32>(xx, yy)) //画布大小
{
image.Mutate(x => x.
BackgroundColor(Rgba32.WhiteSmoke). //画布背景
Draw(
Pens.Dot(Rgba32.HotPink, dianWith), //大小
new SixLabors.Shapes.PathCollection(listPath) //坐标集合
)
);
image.Save($"{path}/9.png"); //保存
}
这里直接利用IImageProcessingContext<TPixel>扩展方法Draw来绘制有规则的点,如图所示:
比较单调,或许您们能做的更好看些;下面来做验证码图片,主要由:画点+画字=验证码图片,这里我封装了一个方法直接生成验证码图片:
/// <summary>
/// 画点+画字=验证码图片
/// </summary>
/// <param name="content">验证码</param>
/// <param name="outImgPath">输出图片路径</param>
/// <param name="fontFilePath">字体文件</param>
/// <param name="x">图片宽度</param>
/// <param name="y">图片高度</param>
public void GetValidCode(
string content = "我是神牛",
string outImgPath = "D:/F/学习/vs2017/netcore/Study.AspNetCore/WebApp02-1/wwwroot/images/10.png",
string fontFilePath = @"D:\F\学习\vs2017\netcore\Study.AspNetCore\WebApp02-1\wwwroot\bak\STKAITI.TTF",
int xx = , int yy = )
{
var dianWith = ; //点宽度
var xx_space = ; //点与点之间x坐标间隔
var yy_space = ; //y坐标间隔
var wenZiLen = content.Length; //文字长度
var maxX = xx / wenZiLen; //每个文字最大x宽度
var prevWenZiX = ; //前面一个文字的x坐标
var size = ;//字体大小 //字体
var install_Family = new FontCollection().Install(
fontFilePath
//@"C:\Windows\Fonts\STKAITI.TTF" //windows系统下字体文件
);
var font = new Font(install_Family, size); //字体 //点坐标
var listPath = new List<IPath>();
for (int i = ; i < xx / xx_space; i++)
{
for (int j = ; j < yy / yy_space; j++)
{
var position = new Vector2(i * xx_space, j * yy_space);
var linerLine = new LinearLineSegment(position, position);
var shapesPath = new SixLabors.Shapes.Path(linerLine);
listPath.Add(shapesPath);
}
} //画图
using (Image<Rgba32> image = new Image<Rgba32>(xx, yy)) //画布大小
{
image.Mutate(x =>
{
//画点
var imgProc = x.BackgroundColor(Rgba32.WhiteSmoke). //画布背景
Draw(
Pens.Dot(Rgba32.HotPink, dianWith), //大小
new SixLabors.Shapes.PathCollection(listPath) //坐标集合
); //逐个画字
for (int i = ; i < wenZiLen; i++)
{
//当前的要输出的字
var nowWenZi = content.Substring(i, ); //文字坐标
var wenXY = new Vector2();
var maxXX = prevWenZiX + (maxX - size);
wenXY.X = new Random().Next(prevWenZiX, maxXX);
wenXY.Y = new Random().Next(, yy - size); prevWenZiX = Convert.ToInt32(Math.Floor(wenXY.X)) + size; //画字
imgProc.DrawText(
nowWenZi, //文字内容
font,
i % > ? Rgba32.HotPink : Rgba32.Red,
wenXY,
TextGraphicsOptions.Default);
}
});
//保存到图片
image.Save(outImgPath);
}
}
通过简单的调用 GetValidCode("我是神牛");return Page(); 能得到如图验证码图片的效果:
文字看起来好像在点的前面,不过没关系只需要把画点和画字的先后顺序修改下就行了,这里不贴图了;
结合RazorPage模板,展示验证码图片
上面一节是生成了验证码图片,当然实际场景中我们是不需要生成验证码物理图片的,只需要返回一个流或base64等方式输出到web界面上就行了,我们可以来看看 Image<TPixel> 保存时候的扩展方法:
//
// 摘要:
// Saves the image to the given stream using the currently loaded image format.
//
// 参数:
// source:
// The source image
//
// filePath:
// The file path to save the image to.
//
// 类型参数:
// TPixel:
// The Pixel format.
//
// 异常:
// T:System.ArgumentNullException:
// Thrown if the stream is null.
public static void Save<TPixel>(this Image<TPixel> source, string filePath) where TPixel : struct, IPixel<TPixel>;
//
// 摘要:
// Saves the image to the given stream using the currently loaded image format.
//
// 参数:
// source:
// The source image
//
// filePath:
// The file path to save the image to.
//
// encoder:
// The encoder to save the image with.
//
// 类型参数:
// TPixel:
// The Pixel format.
//
// 异常:
// T:System.ArgumentNullException:
// Thrown if the encoder is null.
public static void Save<TPixel>(this Image<TPixel> source, string filePath, IImageEncoder encoder) where TPixel : struct, IPixel<TPixel>;
//
// 摘要:
// Saves the image to the given stream using the currently loaded image format.
//
// 参数:
// source:
// The source image
//
// stream:
// The stream to save the image to.
//
// format:
// The format to save the image to.
//
// 类型参数:
// TPixel:
// The Pixel format.
//
// 异常:
// T:System.ArgumentNullException:
// Thrown if the stream is null.
public static void Save<TPixel>(this Image<TPixel> source, Stream stream, IImageFormat format) where TPixel : struct, IPixel<TPixel>;
//
// 摘要:
// Saves the image to the given stream with the bmp format.
//
// 参数:
// source:
// The image this method extends.
//
// stream:
// The stream to save the image to.
//
// 类型参数:
// TPixel:
// The pixel format.
//
// 异常:
// T:System.ArgumentNullException:
// Thrown if the stream is null.
public static void SaveAsBmp<TPixel>(this Image<TPixel> source, Stream stream) where TPixel : struct, IPixel<TPixel>;
//
// 摘要:
// Saves the image to the given stream with the bmp format.
//
// 参数:
// source:
// The image this method extends.
//
// stream:
// The stream to save the image to.
//
// encoder:
// The encoder to save the image with.
//
// 类型参数:
// TPixel:
// The pixel format.
//
// 异常:
// T:System.ArgumentNullException:
// Thrown if the stream is null.
public static void SaveAsBmp<TPixel>(this Image<TPixel> source, Stream stream, BmpEncoder encoder) where TPixel : struct, IPixel<TPixel>;
//
// 摘要:
// Saves the image to the given stream with the gif format.
//
// 参数:
// source:
// The image this method extends.
//
// stream:
// The stream to save the image to.
//
// encoder:
// The options for the encoder.
//
// 类型参数:
// TPixel:
// The pixel format.
//
// 异常:
// T:System.ArgumentNullException:
// Thrown if the stream is null.
public static void SaveAsGif<TPixel>(this Image<TPixel> source, Stream stream, GifEncoder encoder) where TPixel : struct, IPixel<TPixel>;
//
// 摘要:
// Saves the image to the given stream with the gif format.
//
// 参数:
// source:
// The image this method extends.
//
// stream:
// The stream to save the image to.
//
// 类型参数:
// TPixel:
// The pixel format.
//
// 异常:
// T:System.ArgumentNullException:
// Thrown if the stream is null.
public static void SaveAsGif<TPixel>(this Image<TPixel> source, Stream stream) where TPixel : struct, IPixel<TPixel>;
//
// 摘要:
// Saves the image to the given stream with the jpeg format.
//
// 参数:
// source:
// The image this method extends.
//
// stream:
// The stream to save the image to.
//
// encoder:
// The options for the encoder.
//
// 类型参数:
// TPixel:
// The pixel format.
//
// 异常:
// T:System.ArgumentNullException:
// Thrown if the stream is null.
public static void SaveAsJpeg<TPixel>(this Image<TPixel> source, Stream stream, JpegEncoder encoder) where TPixel : struct, IPixel<TPixel>;
//
// 摘要:
// Saves the image to the given stream with the jpeg format.
//
// 参数:
// source:
// The image this method extends.
//
// stream:
// The stream to save the image to.
//
// 类型参数:
// TPixel:
// The pixel format.
//
// 异常:
// T:System.ArgumentNullException:
// Thrown if the stream is null.
public static void SaveAsJpeg<TPixel>(this Image<TPixel> source, Stream stream) where TPixel : struct, IPixel<TPixel>;
//
// 摘要:
// Saves the image to the given stream with the png format.
//
// 参数:
// source:
// The image this method extends.
//
// stream:
// The stream to save the image to.
//
// 类型参数:
// TPixel:
// The pixel format.
//
// 异常:
// T:System.ArgumentNullException:
// Thrown if the stream is null.
public static void SaveAsPng<TPixel>(this Image<TPixel> source, Stream stream) where TPixel : struct, IPixel<TPixel>;
//
// 摘要:
// Saves the image to the given stream with the png format.
//
// 参数:
// source:
// The image this method extends.
//
// stream:
// The stream to save the image to.
//
// encoder:
// The options for the encoder.
//
// 类型参数:
// TPixel:
// The pixel format.
//
// 异常:
// T:System.ArgumentNullException:
// Thrown if the stream is null.
public static void SaveAsPng<TPixel>(this Image<TPixel> source, Stream stream, PngEncoder encoder) where TPixel : struct, IPixel<TPixel>;
//
// 摘要:
// Saves the raw image to the given bytes.
//
// 参数:
// source:
// The source image
//
// buffer:
// The buffer to save the raw pixel data to.
//
// 类型参数:
// TPixel:
// The Pixel format.
//
// 异常:
// T:System.ArgumentNullException:
// Thrown if the stream is null.
public static void SavePixelData<TPixel>(this ImageFrame<TPixel> source, byte[] buffer) where TPixel : struct, IPixel<TPixel>;
//
// 摘要:
// Saves the raw image to the given bytes.
//
// 参数:
// source:
// The source image
//
// 类型参数:
// TPixel:
// The Pixel format.
//
// 返回结果:
// A copy of the pixel data as bytes from this frame.
//
// 异常:
// T:System.ArgumentNullException:
// Thrown if the stream is null.
public static byte[] SavePixelData<TPixel>(this ImageFrame<TPixel> source) where TPixel : struct, IPixel<TPixel>;
//
// 摘要:
// Saves the raw image to the given bytes.
//
// 参数:
// source:
// The source image
//
// buffer:
// The buffer to save the raw pixel data to.
//
// 类型参数:
// TPixel:
// The Pixel format.
//
// 异常:
// T:System.ArgumentNullException:
// Thrown if the stream is null.
public static void SavePixelData<TPixel>(this Image<TPixel> source, byte[] buffer) where TPixel : struct, IPixel<TPixel>;
好吧有点多,我们只需要明白她能转base64,stream,保存为图片等就行了;这里我们将用到 SaveAsPng(Stream) 方法,然后获取他的byte[],如下代码:
/// <summary>
/// 画点+画字=验证码byte[]
/// </summary>
/// <param name="content">验证码</param>
/// <param name="outImgPath">输出图片路径</param>
/// <param name="fontFilePath">字体文件</param>
/// <param name="x">图片宽度</param>
/// <param name="y">图片高度</param>
public byte[] GetValidCodeByte(
string content = "我是神牛",
string fontFilePath = @"D:\F\学习\vs2017\netcore\Study.AspNetCore\WebApp02-1\wwwroot\bak\STKAITI.TTF",
int xx = , int yy = )
{
var bb = default(byte[]);
try
{
var dianWith = ; //点宽度
var xx_space = ; //点与点之间x坐标间隔
var yy_space = ; //y坐标间隔
var wenZiLen = content.Length; //文字长度
var maxX = xx / wenZiLen; //每个文字最大x宽度
var prevWenZiX = ; //前面一个文字的x坐标
var size = ;//字体大小 //字体
var install_Family = new FontCollection().Install(
fontFilePath
//@"C:\Windows\Fonts\STKAITI.TTF" //windows系统下字体文件
);
var font = new Font(install_Family, size); //字体 //点坐标
var listPath = new List<IPath>();
for (int i = ; i < xx / xx_space; i++)
{
for (int j = ; j < yy / yy_space; j++)
{
var position = new Vector2(i * xx_space, j * yy_space);
var linerLine = new LinearLineSegment(position, position);
var shapesPath = new SixLabors.Shapes.Path(linerLine);
listPath.Add(shapesPath);
}
} //画图
using (Image<Rgba32> image = new Image<Rgba32>(xx, yy)) //画布大小
{
image.Mutate(x =>
{
var imgProc = x; //逐个画字
for (int i = ; i < wenZiLen; i++)
{
//当前的要输出的字
var nowWenZi = content.Substring(i, ); //文字坐标
var wenXY = new Vector2();
var maxXX = prevWenZiX + (maxX - size);
wenXY.X = new Random().Next(prevWenZiX, maxXX);
wenXY.Y = new Random().Next(, yy - size); prevWenZiX = Convert.ToInt32(Math.Floor(wenXY.X)) + size; //画字
imgProc.DrawText(
nowWenZi, //文字内容
font,
i % > ? Rgba32.HotPink : Rgba32.Red,
wenXY,
TextGraphicsOptions.Default);
} //画点
imgProc.BackgroundColor(Rgba32.WhiteSmoke). //画布背景
Draw(
Pens.Dot(Rgba32.HotPink, dianWith), //大小
new SixLabors.Shapes.PathCollection(listPath) //坐标集合
);
});
using (MemoryStream stream = new MemoryStream())
{
image.SaveAsPng(stream);
bb = stream.GetBuffer();
}
}
}
catch (Exception ex)
{
}
return bb;
}
该方法返回了一个byte[]数组,然后通过HttpGet方式请求Razor接口,前端就能够获取到这个验证码图片byte[]了;
/// <summary>
/// Get获取验证码图片byte[]
/// </summary>
/// <returns></returns>
public FileResult OnGetValidCode()
{
var codebb = GetValidCodeByte(DateTime.Now.ToString("mmssfff"));
return File(codebb, "image/png");
}
我们通过get请求获取验证码: http://localhost:1120/login?handler=ValidCode ,然后得到如图效果:
本篇内容到此就结束了,如果对您有好的帮助,不妨点个“赞”;一起努力推动NetCore发展吧,谢谢。
ImageSharp一个专注于NetCore平台图像处理的开源项目的更多相关文章
- 我发起了一个 .Net Core 平台上的 开源项目 ShadowDomain 用于 热更新
大家好, 我发起了一个 .Net Core 平台上的 开源项目 ShadowDomain 用于 热更新 . 简单的说, 原理就是 类似 Asp.net 那样 让 当前 WebApp 运行在一个 A ...
- 我发起了一个 用 物理服务器 和 .Net 平台 构建云平台 的 .Net 开源项目
大家好 , 我发起了一个 用 物理服务器 和 .Net 平台 构建云平台 的 .Net 开源项目 . 对 , 用 物理服务器 和 .Net 平台 构建 云平台 . 通过 .Net 构建 分布式 计算集 ...
- 我发起了一个 .Net 平台上的 开源项目 知识图谱 Babana Map 和 文本文件搜索引擎 Babana Search
起因 也是 前几天 有 网友 在 群 里发了 知识图谱 相关的文章, 还有 有 网友 问起 NLog -> LogStash -> Elastic Search 的 问题, ...
- TomatoLog 是一个基于 .NETCore 平台的产品。
TomatoLog TomatoLog 是一个基于 .NETCore 平台的产品. The TomatoLog 是一个中间件,包含客户端.服务端,非常容易使用和部署. 客户端实现了ILoggerFac ...
- 融e学 一个专注于重构知识,培养复合型人才的平台【获取考试答案_破解】
考试系统-融e学-一个专注于重构知识,培养复合型人才的平台.[获取答案] ganquanzhong 背景:今天去完成学校在融e学上开设的必修课和选修课考试,由于自己的时间有限(还有其他的事情要去做). ...
- 转:一个跨WINDOWS LINUX平台的线程类
来源:http://blog.csdn.net/dengxu11/article/details/7232681 继Windows下实现一个CThread封装类之后,这里我再实现一个跨WINDOWS ...
- Composite C1是一个.Net平台上开源专业的CMS开源项目
CompositeC1 4 发布 Composite C1是一个.Net平台上开源专业的CMS开源项目,很多的功能用户界面,面向任务的支持与各种工具协作.当编辑内容时在用户端体验很友好.编辑器与开发者 ...
- 我发起了一个 .Net 平台上的 产生式编程 开源项目 GP.Net
大家好 , 我发起了一个 .Net 平台上的 产生式编程 开源项目 GP.Net . 我们可以先看看一个网友的 代码生成器 项目 : <.Net 代码生成器 for PostgreSql> ...
- 这是一个专注于电脑技术、软件应用、互联网、嵌入式,电子技术行业等的原创IT博客
http://www.choovin.com/ 这是一个专注于电脑技术.软件应用.互联网.嵌入式,电子技术行业等的原创IT博客
随机推荐
- 新CCIE笔记之'口口相传'路由协议
//由于思科所有命令行中没有尖括号"<>"这样的关键字,所以本文中出现命令行中的尖括号中的内容均为注释提示信息,代表此处应该填入那一类数据. 请容许我将RIP和EIGR ...
- 1~N任意三个数最大的最小公倍数(Java版)
最大最小公倍数 如题 话不多说,直接上代码 public class MaxCommonMultiple{ public static void main(String[] args) { Scann ...
- go golang 笔试题 面试题 笔试 面试
go golang 笔试题 面试题 笔试 面试 发现go的笔试题目和面试题目还都是比较少的,于是乎就打算最近总结一下.虽然都不难,但是如果没有准备猛地遇到了还是挺容易踩坑的. 就是几个简单的笔试题目, ...
- 团队作业3——需求改进&系统设计
Deadline: 2017-4-21 22:00PM,以博客发表日期为准 评分基准: 按时交 - 有分,检查的项目包括后文的四个方面 需求&原型改进 系统设计 Alpha任务分配计划 测试计 ...
- 团队作业4——第一次项目冲刺(Alpha版本)第六天and第七天
团队作业4--第一次项目冲刺(Alpha版本)第六天and第七天 第一次项目冲刺(Alpha版本)第六天 一.Daily Scrum Meeting照片 二.燃尽图 1.解释说明横纵坐标代表的含义 ...
- 团队作业4——第一次项目冲刺(Alpha版本) Day3
1.由于大家课程都比较多,时间紧迫,今天最后一节课下课完在教室召开了简短的站立式会议,会议照片如下: 2.Leangoo任务分解图: 3.每个人的工作: 队员 今天已完成的工作 明天计划完成的工作 林 ...
- [转载]请教各位高手光盘版或者U盘版的BT保存配置的问题
这样安装的bt4默认是不能保存配置的,每次你更改了设置,下次重启又没有了.在网上下载一个叫做"saveFile.rar"的压缩包,大小40K左右,这个是bt4能够保存配置关键,当然 ...
- java课设 五子棋代码编写(团队)
1. 团队课程设计博客链接 http://www.cnblogs.com/yzb123/p/7063424.html 2.个人责模块或任务说明 1.主函数编写,设置图形界面 2,设置功能按钮 3.使用 ...
- 201521123060 《Java程序设计》第9周学习总结
1.本周学习总结 1.1 以你喜欢的方式(思维导图或其他)归纳总结异常相关内容. 2.书面作业 本次PTA作业题集异常 1.常用异常 题目5-1 1.1截图你的提交结果(出现学号) 1.2自己以前编写 ...
- Android 跳转系统选择本地视频的功能
今天在项目开发的过程中产品要求添加选择本地视频的功能,于是就翻阅和查找各种资料,进行功能的开发,但是在开发过程中发现,各种不同的品牌的手机跳转至系统选择本地视频的功能结果不太一样,所以我就对一些主流的 ...