MoreRSS

site iconZoDream修改

作者开发了一个名为 ZoDream 的php框架。
请复制 RSS 到你的阅读器,或快速订阅到 :

Inoreader Feedly Follow Feedbin Local Reader

ZoDream的 RSS 预览

基于 SkiaSharp 的轮廓获取

2024-11-22 18:33:46

基于 SkiaSharp 的轮廓获取

源代码

示例

事前须知

本文基于透明图片的透明度获取轮廓;如不透明图片获取轮廓需要先把图片转成灰度图片,根据灰度值获取轮廓。

原理

  1. 需要获取一个物体的起始点,通常是从左至右逐行遍历像素点,获取第一个不透明点
  2. 获取相邻的下一个不透明点,通常是以当前点为中心,顺时针遍历八个方向的点,找到的一个个点就是下一个点。
  3. 初始点第一个方向为正上个方,下一个点的第一个方向就应该是相对与上一个点的方向顺时针转2下。
o c o o
o o b o
o a o o
o o o

// 从 a 找到 b,方向为 1 (0 是正上方)
// 那个 b 就在 a 的 1 方向,a 就在 b 的 5方向
// 但 b 的 6 方向已经被 a 找过了, 所以 b 的起始方向就是 7 
// 总结 b 在 a 的 n 方向,则 b 的起始方向为 n + 6 

代码

/// <summary>
/// 边界算法
/// </summary>
/// <returns></returns>
private static SKPath? TraceContour(SKPixmap pixMap, int beginX, int beginY)
{
    var path = new SKPath();
    path.MoveTo(beginX, beginY);
    var directItems = new int[][] {
        [0, -1], [1, -1], 
                 [1, 0],
        [1, 1], [0, 1], [-1, 1],
        [-1, 0], [-1, -1]
    };
    var beginDirect = 0;
    var isBegin = false;
    var curX = beginX;
    var curY = beginY;
    while (!isBegin)
    {
        var i = 0;
        var direct = beginDirect;
        var hasPoint = false;
        while (i ++ <= directItems.Length)
        {
            var x = curX + directItems[direct][0];
            var y = curY + directItems[direct][1];
            // 判断点是否是透明像素点
            if (IsTransparent(pixMap, x, y))
            {
                direct = (direct + 1) % directItems.Length;
                continue;
            }
            hasPoint = true;
            curX = x;
            curY = y;
            if (curX == beginX && curY == beginY)
            {
                isBegin = true;
                path.Close();
            }
            else
            {
                path.LineTo(curX, curY);
            }
            beginDirect = (direct + 6) % directItems.Length;
            break;
        }
        if (!hasPoint)
        {
            // 所有方向都没有找到下一个不透明点,表明这就是一个孤点
            return null;
        }
    }
    return path;
}

升级版:获取外轮廓,允许点的

/// <summary>
/// 物体轮廓获取
/// </summary>
public class ImageContourTrace
{
    public ImageContourTrace()
    {

    }

    public ImageContourTrace(bool isOutline)
    {
        IsOutline = isOutline;
    }
    /// <summary>
    /// 外边框,即靠近物体的透明区域
    /// </summary>
    public bool IsOutline { get; set; }
    /// <summary>
    /// 是否需要获取一个点
    /// </summary>
    public bool IsAllowDot { get; set; }

    /// <summary>
    /// 获取图片上所有物体轮廓
    /// </summary>
    /// <param name="image"></param>
    /// <returns></returns>
    public async Task<SKPath[]> GetContourAsync(SKBitmap image, CancellationToken token = default)
    {
        using var imagePixMap = image.PeekPixels();
        return await GetContourAsync(imagePixMap, token);
    }

    /// <summary>
    /// 获取图片上所有物体轮廓
    /// </summary>
    /// <param name="image"></param>
    /// <returns></returns>
    public async Task<SKPath[]> GetContourAsync(SKImage image, CancellationToken token = default)
    {
        using var imagePixMap = image.PeekPixels();
        return await GetContourAsync(imagePixMap, token);
    }

    /// <summary>
    /// 获取图片上所有物体轮廓
    /// </summary>
    /// <param name="image"></param>
    /// <returns></returns>
    public Task<SKPath[]> GetContourAsync(SKPixmap pixMap, CancellationToken token = default)
    {
        return Task.Factory.StartNew(() => {
            return GetContour(pixMap, token);
        }, token);
    }
    /// <summary>
    /// 获取所有物体的轮廓
    /// </summary>
    /// <param name="pixMap"></param>
    /// <returns></returns>
    public SKPath[] GetContour(SKPixmap pixMap, CancellationToken token = default)
    {
        var items = new List<SKPath>();
        for (var i = 0; i < pixMap.Height; i++)
        {
            for (var j = 0; j < pixMap.Width; j++)
            {
                if (token.IsCancellationRequested)
                {
                    return [..items];
                }
                if (IsTransparent(pixMap, j, i) || Contains(items, j, i))
                {
                    continue;
                }
                var path = GetContour(pixMap, j, i);
                if (path is null)
                {
                    continue;
                }
                items.Add(path);
            }
        }
        return [.. items];
    }

    /// <summary>
    /// 根据坐标获取轮廓边界算法
    /// </summary>
    /// <param name="pixMap"></param>
    /// <param name="beginX"></param>
    /// <param name="beginY"></param>
    /// <returns></returns>
    public SKPath? GetContour(SKPixmap pixMap, int beginX, int beginY)
    {
        var path = new SKPath();
        path.MoveTo(beginX, beginY - (IsOutline ? 1 : 0));
        var directItems = new int[][] {
            [0, -1], [1, -1],
                        [1, 0],
            [1, 1], [0, 1], [-1, 1],
            [-1, 0], [-1, -1]
        };
        var beginDirect = 0;
        var isBegin = false;
        var curX = beginX;
        var curY = beginY;
        while (!isBegin)
        {
            var i = 0;
            var direct = beginDirect;
            var hasPoint = false;
            while (i++ <= directItems.Length)
            {
                var x = curX + directItems[direct][0];
                var y = curY + directItems[direct][1];
                if (IsTransparent(pixMap, x, y))
                {
                    direct = (direct + 1) % directItems.Length;
                    if (IsOutline)
                    {
                        path.LineTo(x, y);
                    }
                    continue;
                }
                hasPoint = true;
                curX = x;
                curY = y;
                if (curX == beginX && curY == beginY)
                {
                    isBegin = true;
                    path.Close();
                }
                else if (!IsOutline)
                {
                    path.LineTo(curX, curY);
                }
                beginDirect = (direct + 6) % directItems.Length;
                break;
            }
            if (!hasPoint)
            {
                if (IsOutline)
                {
                    path.Close();
                    return path;
                }
                // 所有方向都没有不透明点,就是一个孤点
                return IsAllowDot ? path : null;
            }
        }
        return path;
    }

    private static bool Contains(IEnumerable<SKPath> items, int x, int y)
    {
        foreach (var item in items)
        {
            if (item.Contains(x, y))
            {
                return true;
            }
        }
        return false;
    }

    private static bool IsTransparent(SKPixmap pixMap, int x, int y)
    {
        if (x < 0 || y < 0 || x >= pixMap.Width || y >= pixMap.Height)
        {
               return true;
        }
        return pixMap.GetPixelColor(x, y).Alpha == 0;
    }
}

SkiaSharp 把 pixel byte[] 转成 SKBitmap

2024-08-19 18:54:04

SkiaSharp 把 pixel byte[] 转成 SKBitmap

关键方法

/// <summary>
/// 把像素字节数组转图片
/// </summary>
/// <param name="buffer">像素数组,例如: [r, g, b, a, r, g, b, a ...]</param>
/// <param name="width">图片的宽度,例如: 512</param>
/// <param name="height">图片的高度,例如: 1024</param>
/// <param name="format">指定像素数组的组成方式,例如:SKColorType.Rgba8888</param>
/// <returns></returns>
public static SKBitmap Decode(byte[] buffer, int width, int height, SKColorType format)
{
    var data = SKData.CreateCopy(buffer);
    var newInfo = new SKImageInfo(width, height, format);
    var bitmap = new SKBitmap();
    bitmap.InstallPixels(newInfo, data.Data);
    return bitmap;
}

public static SKImage Decode(byte[] buffer, int width, int height, SKColorType format)
{
    var newInfo = new SKImageInfo(width, height, format);
    var data = SKData.CreateCopy(buffer);
    return SKImage.FromPixels(newInfo, data);
}

解密不知的图片格式

SkiaSharp 默认支持 png jpg 等文件格式,但是一些不支持的文件格式怎么显示呢?

例如:pvr 格式就不能直接解码。

  1. 第一步,先解析 pvr 文件,获取 pvr 文件头得到:图片的宽高,编码方式,数据开始的开始的位置
  2. 读取内容数据
  3. 判断 编码方式 是否被 SkiaSharp 支持,所有支持的颜色编码方式在 SKColorType
  4. 不支持的编码方式,需要先转成支持的,例如转成 SKColorType.Rgba8888, 即:每个像素占四个字节,分别为 [Red,Green,Blue,Alpha],总字节为 宽*高*4, 以逐行横行存储, (行*宽+列)*4 获取像素点的位置
  5. 调用上述方法 Decode(转换后的数据, 宽, 高, SKColorType.Rgba8888) 即可

可能出现的问题

Q: Attempted to read or write protected memory

A: SkiaSharp 是线程安全的,两个线程不应访问同一个对象。SKBitmap 只能在UI主线程中操作,SKImage 则可以在所有子线程中操作

nas 使用 Docker 安装 gogs

2024-08-04 18:43:01

nas 使用 Docker 安装 gogs

环境

NAS设备:绿联DXP4800 Plus

NAS系统:UGOS PRO

第一步:安装 Docker

安装 Docker

第二步:安装 Docker 镜像

无法直接 DockerHub 可以使用 阿里云的镜像加速

DockerHub

配置Dockerhub加速

搜索 gogs 下载

gogs

第三步:安装 gogs

配置自动重启 屏幕截图 2024-08-04 110558.png

配置文件保存位置 屏幕截图 2024-08-04 105940.png

配置端口映射 屏幕截图 2024-08-04 105954.png

访问网页进行GOGS配置 屏幕截图 2024-08-04 110407.png

配置数据库图片来源:https://www.cnblogs.com/yuexiaoyun/articles/11946103.html

配置端口图片来源:https://www.cnblogs.com/yuexiaoyun/articles/11946103.html

一些问题

Q: nas重启后网址无法打开

A:可能是gogs安装时,端口配置错误,可以到gogs的文件保存位置下找到 gogs/conf/app.ini 文件修改 [server]HTTP_PORT3000 即可

ini文件

INI配置

参考

基于docker搭建gogs

复制 android 手机中的文件到电脑

2024-08-04 18:40:38

复制 android 手机中的文件到电脑

方法一:直接复制

使用USB连接电脑

在手机上选择“USB用于传输文件”

优点

  1. 不需要其他操作,简单方便

缺点

  1. 文件一多就会复制很慢很慢

方法二:使用第三方app直接传

缺点

  1. 需要安装app
  2. 传输慢
  3. android/data 下的文件可能无法获取到并传输,如果再手机上把 android/data复制到 Download,可能无法复制目录中的文件

方法三:先再手机上使用第三方app压缩再传输

例如使用 ES文件浏览器

优点

  1. 压缩后,传输会快很多

缺点

  1. 有些文件名中有特殊字符会导致压缩失败

方法三:使用adb pull 下载

需要在电脑中安装 adb

使用USB连接电脑

在手机上选择“USB用于传输文件”

再手机 设置 中打开 开发者模式,开启 USB调试功能

电脑上打开终端命令行

# adb pull <手机中的文件路径> <保存在电脑的位置>
adb pull /storage/emulated/0/Android/data/com.xxx files

优点

  1. 就算文件多,传输也很快

缺点

  1. 一些文件名中有特殊字符会导致传输中断,无法跳过,可能是 linux 和 windows 系统的原因导致的,电脑是 linux 系统的话不会出现这个问题

方法四【推荐】:使用adb压缩下载

注意千万不要使用以下命令

这两种命令的原理就是压缩/storage/emulated/0/Android/data/com.xx不保存直接输出终端,再保存终端输出的内容到 aaa.tar 文件,但是会导致文件出现0xFF 0xFE 和 很多 0x0字节,导致解压缩软件无法读取

adb exec-out tar chf - -C /storage/emulated/0/Android/data/com.xx files > aaa.tar

adb shell 'tar -cf - /storage/emulated/0/Android/data/xx 2>/dev/null' > backup.tar.gz

可以使用先压缩到手机,再下载出来

# 压缩 /storage/emulated/0/Android/data/com.xx 到 /storage/emulated/0/Download/files.tar 文件中
adb exec-out tar chf /storage/emulated/0/Download/files.tar /storage/emulated/0/Android/data/com.xx

# 下载
adb pull /storage/emulated/0/Download/files.tar files.tar

tar 命令

-c: 建立压缩档案
-x:解压
-t:查看内容
-r:向压缩归档文件末尾追加文件
-u:更新原压缩包中的文件
-f: 文件
-h: 解除软链接

所以 tar chf <保存的文件> <要压缩的目录> 就是 tar -c <保存的文件> -h -f <要压缩的目录> 的意思

优点

  1. 所有的文件都能传输
  2. 在手机上压缩更快

缺点

  1. 需要手机空间大

AI学习笔记

2024-06-14 04:34:40

分词原理

将每个字归类为: 词首(B)、词中(M)、词尾(E)、单子词(S)

文字生成原理

输入前一个字,输出后一个字;训练时就是将n个字符的内容 [0, n-1] 作为输入,将 [1, n] 做为预测值。

升级 SiteServer CMS 并迁移到 Linux 服务器

2024-05-07 18:54:50

前言

以前使用 SiteServer CMS 6.8 在 Windows server 12 的服务器上部署了两个企业站,现在需要换到 Linux 服务器上。

SiteServer CMS 6.8 使用的是 Asp.net 不支持Linux 系统,所以需要升级到 SiteServer CMS 7

SiteServer CMS 7 使用的是 .NET Core 支持跨平台。

这里干脆升级到最新的 SiteServer CMS 7.30 (.NET 8)

先决条件

Windows 服务器未过期或拥有Windows电脑并安装了 IIS + MySQL + Asp net core 运行时

在Windows中运行SSCMS

SSCMS老版本升级

概念讲解

SSCMS 的模板语法不需要变动;

主要是数据表的变动;

分步指导

假设旧版本的数据已经在MySQL数据库中

  1. 在Windows上部署 SSCMS 7.30

官网 直接下最新的 windows 版本即可

解压放到一个文件里,例如 D:/sscms

根据 “SSCMS老版本升级” 升级文档进行操作即可

  1. 更新数据表

新建一个文件 D:/sscms/old.json

{
    "Database": {
        "Type": "MySql",
        "ConnectionString": "Server=127.0.0.1;Uid=root;Pwd=root;Database=sqldb_old;SslMode=none;CharSet=utf8"
    }
}

D:/sscms 文件夹中打开终端 PowerShell,运行命令更新数据

.\sscms data backup -d backup -c old.json

.\sscms data update -d backup

新建一个文件 D:/sscms/sscms.json,

注意sqldb_new 数据库必须为空

{
    "Database": {
        "Type": "MySql",
        "ConnectionString": "Server=127.0.0.1;Uid=root;Pwd=root;Database=sqldb_new;SslMode=none;CharSet=utf8"
    }
}

运行命令恢复数据

sscms data restore -d update

完成数据恢复后,需要在浏览器中进入 http://<域名>/ss-admin/syncDatabase/ 数据库升级界面,点击升级按钮,完成数据库升级。

注意: D:/sscms/sscms.json 需要增加 SecurityKey 字段,不然后台登录验证码无法显示

{
    "IsProtectData": false,
    "IsSafeMode": false,
    "SecurityKey": "2cf8fcc7150b6f839784231b8b959217cc0840f20623813b",
    "Database": {
        "Type": "MySql",
        "ConnectionString": "Server=127.0.0.1;Uid=root;Pwd=root;Database=sqldb_new;SslMode=none;CharSet=utf8"
    },
    "Redis": {
        "ConnectionString": ""
    },
    "IsDisablePlugins": false,
    "AdminRestriction": {
        "Host": "",
        "AllowList": [],
        "BlockList": []
    },
    "Cors": {
        "IsOrigins": false,
        "Origins": []
    }
}
  1. 升级中的一些问题

后台登录验证码无法显示

D:/sscms/sscms.json 需要增加 SecurityKey 字段,具体值需要新安装一个SSCMS系统(删除D:/sscms/sscms.json文件即可),复制新的 sscms.json 文件更改 Database.ConnectionString 值即可

老版本的密码不能用了

新安装一个SSCMS系统(删除D:/sscms/sscms.json文件即可),设置管理员密码,然后到数据库中 找到表 siteserver_administrator 复制 PasswordPasswordSalt 两个字段值即可。

后台登录提示字段不存在

需要手动复制新系统的数据表结构。因为SSCMS版本变动表结构也发生了变动。

登录进入显示需要创建站点

需要修改数据表 siteserver_site 的字段 SiteType 值为 web

前台页面导航栏显示不全

需要到后台的栏目管理->编辑栏目手动勾选 栏目组 导航即可

Snipaste_2024-05-07_10-46-29.jpg

前台页面的包含文件不加载

需要到后台的显示管理-> 包含文件管理 添加即可

Snipaste_2024-05-07_10-46-53.jpg

  1. 在 linux 系统安装 netcore 环境

这里使用的是阿里云ECS服务器

使用命令 lsb_release -a 查询的系统信息为

LSB Version:    :core-4.1-amd64:core-4.1-noarch
Distributor ID: AlibabaCloud
Description:    Alibaba Cloud Linux release 3 (Soaring Falcon)
Release:        3
Codename:       SoaringFalcon

所以安装 netcore 的方法参考:在RHEL和CentOS Stream中运行SSCMS

sudo rpm -Uvh https://packages.microsoft.com/config/centos/8/packages-microsoft-prod.rpm

sudo dnf install aspnetcore-runtime-8.0

# 验证dotnet core runtime是否安装成功
dotnet --info
  1. 配置站点

假设已安装过 Nginx

a站点

nginx 配置

server {
        listen       80;
        server_name  a.com;
        root         /data/a; # 设置站点根目录
        location / {
            proxy_pass         http://localhost:5000;
            proxy_http_version 1.1;
            proxy_set_header   Upgrade $http_upgrade;
            proxy_set_header   Connection keep-alive;
            proxy_set_header   Host $host;
            proxy_cache_bypass $http_upgrade;
            proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header   X-Forwarded-Proto $scheme;
            proxy_set_header   X-Real-IP $remote_addr;
            proxy_connect_timeout       600;
            proxy_send_timeout          600;
            proxy_read_timeout          600;
            send_timeout                600;
        }
    }

a 站点的 /data/a/sscms.json

{
    "IsProtectData": false,
    "IsSafeMode": false,
    "SecurityKey": "2cf8fcc7150b6f839784231b8b959217cc0840f20623813b",
    "Database": {
        "Type": "MySql",
        "ConnectionString": "Server=127.0.0.1;Uid=root;Pwd=root;Database=sqldb_a;SslMode=none;CharSet=utf8"
    },
    "Redis": {
        "ConnectionString": ""
    },
    "IsDisablePlugins": false,
    "AdminRestriction": {
        "Host": "",
        "AllowList": [],
        "BlockList": []
    },
    "Cors": {
        "IsOrigins": false,
        "Origins": []
    }
}

创建 a 站点的服务文件 /etc/systemd/system/a.service

[Unit]
Description=a

[Service]
WorkingDirectory=/data/a
ExecStart=/usr/bin/dotnet /data/a/SSCMS.Web.dll
Restart=always
# Restart service after 10 seconds if the sscms service crashes:
RestartSec=10
KillSignal=SIGINT
SyslogIdentifier=sscms
User=root
Environment=ASPNETCORE_ENVIRONMENT=Production
Environment=DOTNET_PRINT_TELEMETRY_MESSAGE=false

[Install]
WantedBy=multi-user.target

b 站点

a站点

nginx 配置

server {
        listen       80;
        server_name  b.com;
        root         /data/b; # 设置站点根目录

        location / {
            proxy_pass         http://localhost:5001;
            proxy_http_version 1.1;
            proxy_set_header   Upgrade $http_upgrade;
            proxy_set_header   Connection keep-alive;
            proxy_set_header   Host $host;
            proxy_cache_bypass $http_upgrade;
            proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header   X-Forwarded-Proto $scheme;
            proxy_set_header   X-Real-IP $remote_addr;
            proxy_connect_timeout       600;
            proxy_send_timeout          600;
            proxy_read_timeout          600;
            send_timeout                600;
        }
    }

b 站点的 /data/b/sscms.json

{
    "Urls": "http://localhost:5001",
    "IsProtectData": false,
    "IsSafeMode": false,
    "SecurityKey": "2cf8fcc7150b6f839784231b8b959217cc0840f20623813b",
    "Database": {
        "Type": "MySql",
        "ConnectionString": "Server=127.0.0.1;Uid=root;Pwd=root;Database=sqldb_b;SslMode=none;CharSet=utf8"
    },
    "Redis": {
        "ConnectionString": ""
    },
    "IsDisablePlugins": false,
    "AdminRestriction": {
        "Host": "",
        "AllowList": [],
        "BlockList": []
    },
    "Cors": {
        "IsOrigins": false,
        "Origins": []
    }
}

创建 b 站点的服务文件 /etc/systemd/system/b.service

[Unit]
Description=b

[Service]
WorkingDirectory=/data/b
ExecStart=/usr/bin/dotnet /data/b/SSCMS.Web.dll
Restart=always
# Restart service after 10 seconds if the sscms service crashes:
RestartSec=10
KillSignal=SIGINT
SyslogIdentifier=sscms
User=root
Environment=ASPNETCORE_ENVIRONMENT=Production
Environment=DOTNET_PRINT_TELEMETRY_MESSAGE=false

[Install]
WantedBy=multi-user.target
  1. 启动服务
sudo systemctl enable a.service

sudo systemctl start a.service

sudo systemctl status a.service # 验证服务是否启动

Snipaste_2024-05-07_10-43-12.jpg

没有报错即配置正确,

b站点服务同理

  1. 导入数据

这里需要注意

在 Windows 中数据表表名都是小写,但是在Linux 环境下是区分大小写的,所以需要手动更改 sql 文件中的数据库表名,再导入

例如 siteserver_administratorsinroles 需要改为 siteserver_AdministratorsInRoles

具体对应表需要重新再 linux 下安装即可

  1. 导入模板文件、资源文件

总结

升级过程还是比较繁琐的,需要有耐心。