MoreRSS

site iconZoDream修改

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

Inoreader Feedly Follow Feedbin Local Reader

ZoDream的 RSS 预览

密码本开发笔记之读写与保存

2025-03-06 22:37:16

密码本开发笔记之读写与保存

第一版 读写共用同一个原始文件

每条记录可以看作一个数据块

struct Chunk {
    int id;
    long offset;
    long length;
}

顺序读取每一块并记录每一块的起始位置和数据长度。

但是更新写入就有点复杂了。

  1. 新增,直接在文件流末尾写入。
  2. 删除,找到下一块的位置,然后循环读取后续部分的数据前移一段位置,然后更新文件的长度,最后还要更新每一块新的起始位置。
  3. 更新,需要先算出数据的长度,然后判断要移除部分空间还是扩张部分空间,然后写入数据,最后还要更新每一块的起始位置。

缺点

  1. 频繁读写,动不动就要移动子节。
  2. 实时写入,撤销麻烦。

第二版 读原始文件,写临时文件

先读取数据块,并标记数据块是原始文件。

struct Chunk {
    int id;
    bool isTemporarySource;
    long offset;
    long length;
}

所有未保存的删除写入更新,都是发生在临时文件中。

  1. 新增,在临时文件末尾写入。
  2. 删除,直接删除一个数据块记录进行了,不需要进行文件读写。
  3. 更新,在临时文件末尾写入,并更新数据块记录为临时文件和新的起始位置及长度。
  4. 保存,获取所有数据块记录从0循环写入到原始文件中,最后更新原始文件的长度。

优点

  1. 减少了对原始文件的写入
  2. 不在自动保存,可以撤销。

基于 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] 做为预测值。