zodream梦想开源/个人编程日记

zodream梦想开源/个人编程日记

简单的个人编程日记

angular 21 升级使用 signals 方案笔记

<h1><a href="https://zodream.cn/blog/0_0/tag/angular.html" title="angular">angular</a> 21 升级使用 signals 方案笔记</h1> <p><code>signals</code> 提供新的数据绑定和变更检测机制。从 <code>angular 17</code> 就开始引入了,</p> <p>优点:</p> <ol> <li>更精细的响应检测。</li> </ol> <p>缺点:</p> <ol> <li>变化太大,</li> </ol> <h2>signal</h2> <p>基本的绑定,可读可写。</p> <p><code>angular 17</code> 之前:</p> <pre><code class="language-ts">@Component({ standalone: false, selector: 'app-example', template: `{{ text }}`, styleUrls: ['./example.component.scss'] }) export class ExampleComponent { public text = 'hi'; public tap() { this.text = 'hello'; } }</code></pre> <p><code>angular 17</code> 之后:</p> <pre><code class="language-ts">@Component({ standalone: false, selector: 'app-example', template: `{{ text() }}`, styleUrls: ['./example.component.scss'] }) export class ExampleComponent { public readonly text = signal&amp;lt;string&amp;gt;('hi'); public tap() { this.text.set('hello'); this.text.update(v =&amp;gt; { return 'hello'; }); } }</code></pre> <h3>注意</h3> <ol> <li>使用 <code>set</code> 或 <code>update</code> 更新值。</li> <li>当值是对象时,使用 <code>update</code> 更新某个属性,必须生成新的对象,才能有效检测<pre><code class="language-ts"> public readonly data = signal({ a: 1, b: 2 }); // 错误示范 this.data.update(v =&amp;gt; { v.a = 3 return v; });</code></pre> </li> </ol> <p>// 正确使用 this.data.update(v =&amp;gt; { v.a = 3; return {...v}; // 或者 return {...v, a: 3}; });</p> <pre><code>3. 当值时数组时,使用 `update` 新增或删除某一项 ```ts public readonly items = signal&amp;lt;number[]&amp;gt;([]); // 错误示范 this.items.update(v =&amp;gt; { v.push(1); v.pop(); return v; }); // 正确使用 this.items.update(v =&amp;gt; { v.push(1); return [...v]; // 或者 return [...v, 1]; });</code></pre> <h2>input、output、model</h2> <p><code>input</code> 相当于 <code>@Input()</code>, 但是可读不可写,增加了自定义转换</p> <p><code>output</code> 相当于 <code>@Output()</code>,</p> <p><code>model</code> 相对于 <code>@Input()</code> + <code>@Output()</code>,可读可写</p> <p><code>angular 17</code> 之前:</p> <pre><code class="language-ts">@Component({ standalone: false, selector: 'app-example', template: `{{ text }}`, styleUrls: ['./example.component.scss'] }) export class ExampleComponent { @Input() public text = 'hi'; @Output() public textChange = new EventEmitter(); }</code></pre> <p><code>angular 17</code> 之后:</p> <pre><code class="language-ts">@Component({ standalone: false, selector: 'app-example', template: `{{ text() }}`, styleUrls: ['./example.component.scss'] }) export class ExampleComponent { public readonly text = input('hi', {tranform: parseInt}); public readonly textChange = output(); public readonly text = model(''); }</code></pre> <h2>form</h2> <pre><code class="language-ts">@Component({ standalone: false, selector: 'app-example', template: `&amp;lt;input type="number" [field]="form.a"&amp;gt;`, styleUrls: ['./example.component.scss'] }) export class ExampleComponent { public readonly form = form(sinal({ a: 1 }), schemaPath =&amp;gt; { required(schemaPath.a); }) } </code></pre> <p>使用 <code>[field]</code> 精选表单绑定,不能自定义 <code>name</code> 属性,</p> <h3>注意</h3> <ol> <li>表单 <code>name</code> 属性自动生成 类似于 <code>[项目名].form1.a</code>,如果介意 <code>name</code>,则推荐使用 原本 <code>FormBuilder</code> 方式</li> <li>一些值限制的变化, <code>select</code> 的值为 <code>string</code>,<code>input type=checkbox</code> 为 <code>boolean</code>, <code>type=number</code> 为 <code>number</code>, 其他则必须为 <code>string</code></li> </ol> <h2>effect</h2> <p>检测 值的变化,替代 <code>ngOnChanges</code></p> <pre><code class="language-ts">@Component({ standalone: false, selector: 'app-example', template: `{{ text() }}`, styleUrls: ['./example.component.scss'] }) export class ExampleComponent { public readonly text = model(''); constructor() { let previousText = ''; effect(() =&amp;gt; { this.text(); // 当 text 发生变化时,TODO previousText = this.text(); // 使用此方法实现值的前后变化检测 }); } }</code></pre> <h2>computed</h2> <p>关联变化,提供值变化前后对比</p> <p><code>angular 17</code> 之前:</p> <pre><code class="language-ts">@Component({ standalone: false, selector: 'app-example', template: `{{ text }} {{ twoText }}`, styleUrls: ['./example.component.scss'] }) export class ExampleComponent { public text = 'hi'; public get twoText() { return this.text + ', two'; } public tap() { this.text = 'hello'; } }</code></pre> <p><code>angular 17</code> 之后:</p> <pre><code class="language-ts">@Component({ standalone: false, selector: 'app-example', template: `{{ text() }} {{ twoText() }}`, styleUrls: ['./example.component.scss'] }) export class ExampleComponent { public readonly text = signal&amp;lt;string&amp;gt;('hi'); public readonly twoText = computed(() =&amp;gt; { return this.text() + ', two'; }); public tap() { this.text.set('hello'); this.text.update(v =&amp;gt; { return 'hello'; }); } }</code></pre> <h2>与 signals 使用时失效的旧方法</h2> <p><code>@HostBinding</code> 无法与 <code>signal</code> 等使用</p> <pre><code class="language-ts">@HostBinding('class.open') // 无效 public readonly toggle = model(false);</code></pre> <p>可以使用 <code>effect</code> 同步或</p> <pre><code class="language-ts"> @Component({ standalone: false, selector: 'app-example', template: ``, styleUrls: ['./example.component.scss'], host: { '[class.open]': 'toggle()' } }) export class ExampleComponent { public readonly toggle = model(false); }</code></pre>

2025/12/30
阅读更多

文件解析笔记

<h1>文件解析笔记</h1> <h2>工具</h2> <p><a href="https://zodream.cn/to/0_0/url/aHR0cHM6Ly9naXRodWIuY29tL1dlcldvbHYvSW1IZXg.html">ImHex</a></p> <h2>基础知识</h2> <table> <thead> <tr> <th>数据类型</th> <th>子节数</th> <th>说明</th> </tr> </thead> <tbody> <tr> <td>byte</td> <td>1</td> <td>占 8 个二进制字符,特殊知识:以 <code>0x80</code> 为分界</td> </tr> <tr> <td>sbyte</td> <td>1</td> <td>占 8 个二进制字符</td> </tr> <tr> <td>short</td> <td>2</td> </tr> <tr> <td>ushort</td> <td>2</td> </tr> <tr> <td>int</td> <td>4</td> </tr> <tr> <td>uint</td> <td>4</td> </tr> <tr> <td>float</td> <td>4</td> </tr> <tr> <td>long</td> <td>8</td> </tr> <tr> <td>ulong</td> <td>8</td> </tr> <tr> <td>double</td> <td>8</td> </tr> <tr> <td>char</td> <td>1 、 2</td> <td>在 c 等低级语言中占 1 个子节,在 c# 等高级语言中占 2 子节</td> </tr> <tr> <td>string</td> <td>无固定长度</td> <td>一种是定义长度,再取长度的字节数转,另一种是以 <code>0x0</code> 为结束符</td> </tr> <tr> <td>leb128</td> <td>无固定长度</td> <td>每次读一个子节, 子节值大于等于 <code>0x80</code> 表示有需要读下一个子节</td> </tr> </tbody> </table> <h3>端</h3> <p>主要为大端小端,主要是指有多个固定字节数的数据类型的字节写入顺序不同, <code>byte</code> <code>sbyte</code> 只有一个字节,没有大小端之分;<code>string</code> <code>char</code> <code>leb128</code> 都不固定长度,依赖前一个字节指示是否有下一个字节,所以无法分大小端。</p> <p>大端(BigEndian, 简写BE),高位写在前面,反转过来写的意思,例如 <code>(ushort)1</code> 转成字节数组是 <code>[0x1, 0x0]</code> 写入流时是先写 <code>0x0</code> 在写入 <code>0x1</code></p> <p>小端(LittleEndian, 简写LE),低位写在前面,顺着字节数组写的意思,例如 <code>(ushort)1</code> 转成字节数组是 <code>[0x1, 0x0]</code> 写入流时是先写 <code>0x1</code> 在写入 <code>0x0</code></p> <p>一般来说:网络字节流的是大端,计算机文件流是小端。</p> <h3>字符串编码</h3> <p><code>Unicode</code> 固定文件头 0xFF 0xFE,大端则是 0xFE 0xFF <code>UTF8</code> 分有头和无头,有固定头 0xEF 0xBB 0xBF</p> <p>无头编码,主要根据 <code>char</code>(两个字节) 存储方式判断,一般来说 0x0-0x7F 是指占用一个字节,0x80-0xFF 根据定义可以指示多个字节</p> <p>UTF8</p> <table> <thead> <tr> <th>字节数</th> <th>字节分布</th> </tr> </thead> <tbody> <tr> <td>1</td> <td>0x00-0x7F</td> </tr> <tr> <td>2</td> <td>0xC2-0xDF 0x80-0xBF</td> </tr> <tr> <td>3</td> <td>0xE0 0xA0-0xBF 0x80-0xBF</td> </tr> <tr> <td>3</td> <td>0xE1-0xEC 0x80-0xBF 0x80-0xBF</td> </tr> <tr> <td>3</td> <td>0xED 0x80-0x9F 0x80-0xBF</td> </tr> <tr> <td>3</td> <td>0xEE-0xEF 0x80-0xBF 0x80-0xBF</td> </tr> <tr> <td>4</td> <td>0xF0 0x90-0xBF 0x80-0xBF 0x80-0xBF</td> </tr> <tr> <td>4</td> <td>0xF1-0xF3 0x80-0xBF 0x80-0xBF 0x80-0xBF</td> </tr> <tr> <td>4</td> <td>0xF4 0x80-0x8F 0x80-0xBF 0x80-0xBF</td> </tr> </tbody> </table> <h3>压缩</h3> <p>一般是固定的头,固定几个字节指示</p> <ol> <li>无损压缩</li> </ol> <table> <thead> <tr> <th>算法类型</th> <th>代表算法</th> <th>适用场景</th> </tr> </thead> <tbody> <tr> <td>字典编码</td> <td>LZ77, LZ78, LZW</td> <td>重复数据压缩(文本)</td> </tr> <tr> <td>熵编码</td> <td>Huffman, 算术编码</td> <td>通过加入长度来表示重复字节或字节数组</td> </tr> </tbody> </table> <p>图片无损压缩: 对单个像素点(R|G|B|A)进行处理,例如不透明图片就可以只保存(R|G|B)三个颜色值,更进一步有单个色值的,例如只保存(R)通道</p> <ol> <li>有损压缩</li> </ol> <p>一般只针对特殊领域,例如图像</p> <p>图象的有损压缩:分成一个个(width*height)小的区域进行近似关联处理,常见的是分成 (4*4) 的小区域,每个区域处理成 8 个字节</p> <h3>加密</h3> <p>加密算法</p> <p>常见的加密算法:</p> <ol> <li>对称加密:DES、AES</li> <li>非对称加密:RSA、ECC</li> </ol> <p>这些复杂的加密算法只适用于文件少且重要的,速度较慢、内存占用较高</p> <p>一般对速度和性能有要求的就简单的使用xor(异或处理)</p> <h2>基本步骤</h2> <ol> <li>找文件的文件头,一般都是固定的内容,有多个同类的文件就很容易识别出来</li> <li>找文本字符串,可用用做定位用,除非加密的文件,找到一个连续的文本,多切换编码试试,查看文本结尾后一个子节是不是 <code>0x0</code>, 不是的话就肯定在前面标记了长度,</li> <li>长度首先考虑 <code>byte</code> 再考虑 <code>leb128</code> 再 <code>uint</code> <code>int</code> <code>long</code></li> <li>如果判断是 <code>leb128</code>, 那就可以肯定 其他长度都用的 <code>leb128</code>, 甚至 <code>uint</code> <code>ulong</code> 等值也转成了 <code>leb128</code> </li> <li>找可能的分隔标记符,看见大片有规律的重复出现的子节就可靠是无意义的分隔符</li> <li>找 <code>0x0</code>, <code>0x0</code> 多用于补位,例如数据类型固定占用多个子节,不足就是 <code>0x0</code>,还有就是补数据内容的长度</li> <li>多数数据都是重复的数据结构,即属性个数一样,用不同的数据类型去算个数是否一致。</li> </ol>

2025/9/11
阅读更多

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

<h1>密码本开发笔记之读写与保存</h1> <h2>第一版 读写共用同一个原始文件</h2> <p>每条记录可以看作一个数据块</p> <pre><code class="language-c">struct Chunk { int id; long offset; long length; }</code></pre> <p>顺序读取每一块并记录每一块的起始位置和数据长度。</p> <p>但是更新写入就有点复杂了。</p> <ol> <li>新增,直接在文件流末尾写入。</li> <li>删除,找到下一块的位置,然后循环读取后续部分的数据前移一段位置,然后更新文件的长度,最后还要更新每一块新的起始位置。</li> <li>更新,需要先算出数据的长度,然后判断要移除部分空间还是扩张部分空间,然后写入数据,最后还要更新每一块的起始位置。</li> </ol> <h3>缺点</h3> <ol> <li>频繁读写,动不动就要移动子节。</li> <li>实时写入,撤销麻烦。</li> </ol> <h2>第二版 读原始文件,写临时文件</h2> <p>先读取数据块,并标记数据块是原始文件。</p> <pre><code class="language-c">struct Chunk { int id; bool isTemporarySource; long offset; long length; }</code></pre> <p>所有未保存的删除写入更新,都是发生在临时文件中。</p> <ol> <li>新增,在临时文件末尾写入。</li> <li>删除,直接删除一个数据块记录进行了,不需要进行文件读写。</li> <li>更新,在临时文件末尾写入,并更新数据块记录为临时文件和新的起始位置及长度。</li> <li>保存,获取所有数据块记录从0循环写入到原始文件中,最后更新原始文件的长度。</li> </ol> <h3>优点</h3> <ol> <li>减少了对原始文件的写入</li> <li>不在自动保存,可以撤销。</li> </ol>

2025/3/6
阅读更多

基于 SkiaSharp 的轮廓获取

<h1>基于 SkiaSharp 的轮廓获取</h1> <p><a href="https://zodream.cn/to/0_0/url/aHR0cHM6Ly9naXRodWIuY29tL3p4NjQ4MzgzMDc5L1pvRHJlYW0uU2hhcmVkL2Jsb2IvbWFpbi9ab0RyZWFtLlNoYXJlZC5EcmF3aW5nL0ltYWdlQ29udG91clRyYWNlLmNz.html">源代码</a></p> <p><a href="https://zodream.cn/to/0_0/url/aHR0cHM6Ly9naXRodWIuY29tL3p4NjQ4MzgzMDc5L1pvRHJlYW0uVGV4dHVyZVBhY2tlci9jb21taXQvNjhmZWZhOWFkYmRmOGQ5MWJjMTA3ZTc4NmYyM2U3Y2ZlN2RhMmQwZA.html">示例</a></p> <h2>事前须知</h2> <p>本文基于透明图片的透明度获取轮廓;如不透明图片获取轮廓需要先把图片转成灰度图片,根据灰度值获取轮廓。</p> <h2>原理</h2> <ol> <li>需要获取一个物体的起始点,通常是从左至右逐行遍历像素点,获取第一个不透明点</li> <li>获取相邻的下一个不透明点,通常是以当前点为中心,顺时针遍历八个方向的点,找到的一个个点就是下一个点。</li> <li>初始点第一个方向为正上个方,下一个点的第一个方向就应该是相对与上一个点的方向顺时针转2下。</li> </ol> <pre><code>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 </code></pre> <h2>代码</h2> <pre><code class="language-c#">/// &amp;lt;summary&amp;gt; /// 边界算法 /// &amp;lt;/summary&amp;gt; /// &amp;lt;returns&amp;gt;&amp;lt;/returns&amp;gt; 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 ++ &amp;lt;= 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 &amp;amp;&amp;amp; curY == beginY) { isBegin = true; path.Close(); } else { path.LineTo(curX, curY); } beginDirect = (direct + 6) % directItems.Length; break; } if (!hasPoint) { // 所有方向都没有找到下一个不透明点,表明这就是一个孤点 return null; } } return path; } </code></pre> <h2>升级版:获取外轮廓,允许点的</h2> <pre><code class="language-c#">/// &amp;lt;summary&amp;gt; /// 物体轮廓获取 /// &amp;lt;/summary&amp;gt; public class ImageContourTrace { public ImageContourTrace() { } public ImageContourTrace(bool isOutline) { IsOutline = isOutline; } /// &amp;lt;summary&amp;gt; /// 外边框,即靠近物体的透明区域 /// &amp;lt;/summary&amp;gt; public bool IsOutline { get; set; } /// &amp;lt;summary&amp;gt; /// 是否需要获取一个点 /// &amp;lt;/summary&amp;gt; public bool IsAllowDot { get; set; } /// &amp;lt;summary&amp;gt; /// 获取图片上所有物体轮廓 /// &amp;lt;/summary&amp;gt; /// &amp;lt;param name="image"&amp;gt;&amp;lt;/param&amp;gt; /// &amp;lt;returns&amp;gt;&amp;lt;/returns&amp;gt; public async Task&amp;lt;SKPath[]&amp;gt; GetContourAsync(SKBitmap image, CancellationToken token = default) { using var imagePixMap = image.PeekPixels(); return await GetContourAsync(imagePixMap, token); } /// &amp;lt;summary&amp;gt; /// 获取图片上所有物体轮廓 /// &amp;lt;/summary&amp;gt; /// &amp;lt;param name="image"&amp;gt;&amp;lt;/param&amp;gt; /// &amp;lt;returns&amp;gt;&amp;lt;/returns&amp;gt; public async Task&amp;lt;SKPath[]&amp;gt; GetContourAsync(SKImage image, CancellationToken token = default) { using var imagePixMap = image.PeekPixels(); return await GetContourAsync(imagePixMap, token); } /// &amp;lt;summary&amp;gt; /// 获取图片上所有物体轮廓 /// &amp;lt;/summary&amp;gt; /// &amp;lt;param name="image"&amp;gt;&amp;lt;/param&amp;gt; /// &amp;lt;returns&amp;gt;&amp;lt;/returns&amp;gt; public Task&amp;lt;SKPath[]&amp;gt; GetContourAsync(SKPixmap pixMap, CancellationToken token = default) { return Task.Factory.StartNew(() =&amp;gt; { return GetContour(pixMap, token); }, token); } /// &amp;lt;summary&amp;gt; /// 获取所有物体的轮廓 /// &amp;lt;/summary&amp;gt; /// &amp;lt;param name="pixMap"&amp;gt;&amp;lt;/param&amp;gt; /// &amp;lt;returns&amp;gt;&amp;lt;/returns&amp;gt; public SKPath[] GetContour(SKPixmap pixMap, CancellationToken token = default) { var items = new List&amp;lt;SKPath&amp;gt;(); for (var i = 0; i &amp;lt; pixMap.Height; i++) { for (var j = 0; j &amp;lt; 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]; } /// &amp;lt;summary&amp;gt; /// 根据坐标获取轮廓边界算法 /// &amp;lt;/summary&amp;gt; /// &amp;lt;param name="pixMap"&amp;gt;&amp;lt;/param&amp;gt; /// &amp;lt;param name="beginX"&amp;gt;&amp;lt;/param&amp;gt; /// &amp;lt;param name="beginY"&amp;gt;&amp;lt;/param&amp;gt; /// &amp;lt;returns&amp;gt;&amp;lt;/returns&amp;gt; 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++ &amp;lt;= 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 &amp;amp;&amp;amp; 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&amp;lt;SKPath&amp;gt; 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 &amp;lt; 0 || y &amp;lt; 0 || x &amp;gt;= pixMap.Width || y &amp;gt;= pixMap.Height) { return true; } return pixMap.GetPixelColor(x, y).Alpha == 0; } }</code></pre>

2024/11/22
阅读更多

SkiaSharp 把 pixel byte[] 转成 SKBitmap

<h1>SkiaSharp 把 pixel byte[] 转成 SKBitmap</h1> <h2>关键方法</h2> <pre><code class="language-c#">/// &amp;lt;summary&amp;gt; /// 把像素字节数组转图片 /// &amp;lt;/summary&amp;gt; /// &amp;lt;param name="buffer"&amp;gt;像素数组,例如: [r, g, b, a, r, g, b, a ...]&amp;lt;/param&amp;gt; /// &amp;lt;param name="width"&amp;gt;图片的宽度,例如: 512&amp;lt;/param&amp;gt; /// &amp;lt;param name="height"&amp;gt;图片的高度,例如: 1024&amp;lt;/param&amp;gt; /// &amp;lt;param name="format"&amp;gt;指定像素数组的组成方式,例如:SKColorType.Rgba8888&amp;lt;/param&amp;gt; /// &amp;lt;returns&amp;gt;&amp;lt;/returns&amp;gt; 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); }</code></pre> <h2>解密不知的图片格式</h2> <p><code>SkiaSharp</code> 默认支持 <code>png</code> <code>jpg</code> 等文件格式,但是一些不支持的文件格式怎么显示呢?</p> <p>例如:<code>pvr</code> 格式就不能直接解码。</p> <ol> <li>第一步,先解析 pvr 文件,获取 pvr 文件头得到:图片的宽高,编码方式,数据开始的开始的位置</li> <li>读取内容数据</li> <li>判断 编码方式 是否被 <code>SkiaSharp</code> 支持,所有支持的颜色编码方式在 <code>SKColorType</code> 中</li> <li>不支持的编码方式,需要先转成支持的,例如转成 <code>SKColorType.Rgba8888</code>, 即:每个像素占四个字节,分别为 [Red,Green,Blue,Alpha],总字节为 <code>宽*高*4</code>, 以逐行横行存储, <code>(行*宽+列)*4</code> 获取像素点的位置</li> <li>调用上述方法 <code>Decode(转换后的数据, 宽, 高, SKColorType.Rgba8888)</code> 即可</li> </ol> <h2>可能出现的问题</h2> <h3>Q: Attempted to read or write protected memory</h3> <p>A: SkiaSharp 是线程安全的,两个线程不应访问同一个对象。SKBitmap 只能在UI主线程中操作,SKImage 则可以在所有子线程中操作</p>

2024/8/19
阅读更多

nas 使用 Docker 安装 gogs

<h1>nas 使用 Docker 安装 gogs</h1> <h2>环境</h2> <p>NAS设备:绿联DXP4800 Plus</p> <p>NAS系统:UGOS PRO</p> <h2>第一步:安装 Docker</h2> <p><img src="https://zodream.cn/assets/upload/image/20240804/1722741176.jpg" title="安装 Docker" alt="安装 Docker" /></p> <h2>第二步:安装 Docker 镜像</h2> <p>无法直接 DockerHub 可以使用 <a href="https://zodream.cn/to/0_0/url/aHR0cHM6Ly9oZWxwLmFsaXl1bi5jb20vemgvYWNyL3VzZXItZ3VpZGUvYWNjZWxlcmF0ZS10aGUtcHVsbHMtb2YtZG9ja2VyLW9mZmljaWFsLWltYWdlcw.html">阿里云的镜像加速</a> </p> <p><img src="https://zodream.cn/assets/upload/image/20240804/1722741199.png" title="DockerHub" alt="DockerHub" /></p> <p><img src="https://zodream.cn/assets/upload/image/20240804/1722741302.png" title="配置Dockerhub加速" alt="配置Dockerhub加速" /></p> <p>搜索 <code>gogs</code> 下载 </p> <p><img src="https://zodream.cn/assets/upload/image/20240804/1722741375.png" title="gogs" alt="gogs" /></p> <h2>第三步:安装 gogs</h2> <p>配置自动重启 <img src="https://zodream.cn/assets/upload/image/20240804/1722741672.png" title="屏幕截图 2024-08-04 110558.png" alt="屏幕截图 2024-08-04 110558.png" /></p> <p>配置文件保存位置 <img src="https://zodream.cn/assets/upload/image/20240804/1722741699.png" title="屏幕截图 2024-08-04 105940.png" alt="屏幕截图 2024-08-04 105940.png" /></p> <p>配置端口映射 <img src="https://zodream.cn/assets/upload/image/20240804/1722741713.png" title="屏幕截图 2024-08-04 105954.png" alt="屏幕截图 2024-08-04 105954.png" /></p> <p>访问网页进行GOGS配置 <img src="https://zodream.cn/assets/upload/image/20240804/1722741768.png" title="屏幕截图 2024-08-04 110407.png" alt="屏幕截图 2024-08-04 110407.png" /></p> <p><img src="https://zodream.cn/assets/upload/image/20240804/1722741825.png" title="配置数据库" alt="配置数据库" /> <a href="https://zodream.cn/to/0_0/url/aHR0cHM6Ly93d3cuY25ibG9ncy5jb20veXVleGlhb3l1bi9hcnRpY2xlcy8xMTk0NjEwMy5odG1s.html">图片来源:https://www.cnblogs.com/yuexiaoyun/articles/11946103.html</a></p> <p><img src="https://zodream.cn/assets/upload/image/20240804/1722741903.png" title="配置端口" alt="配置端口" /> <a href="https://zodream.cn/to/0_0/url/aHR0cHM6Ly93d3cuY25ibG9ncy5jb20veXVleGlhb3l1bi9hcnRpY2xlcy8xMTk0NjEwMy5odG1s.html">图片来源:https://www.cnblogs.com/yuexiaoyun/articles/11946103.html</a></p> <h2>一些问题</h2> <p>Q: nas重启后网址无法打开</p> <p>A:可能是gogs安装时,端口配置错误,可以到gogs的文件保存位置下找到 <code>gogs/conf/app.ini</code> 文件修改 <code>[server]HTTP_PORT</code>为 <code>3000</code> 即可</p> <p><img src="https://zodream.cn/assets/upload/image/20240804/1722742124.png" title="ini文件" alt="ini文件" /></p> <p><img src="https://zodream.cn/assets/upload/image/20240804/1722742186.png" title="INI配置" alt="INI配置" /></p> <h2>参考</h2> <p>【<a href="https://zodream.cn/to/0_0/url/aHR0cHM6Ly93d3cuY25ibG9ncy5jb20veXVleGlhb3l1bi9hcnRpY2xlcy8xMTk0NjEwMy5odG1s.html">基于docker搭建gogs</a>】</p>

2024/8/4
阅读更多

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

<h1>复制 android 手机中的文件到电脑</h1> <h2>方法一:直接复制</h2> <p>使用USB连接电脑</p> <p>在手机上选择“USB用于传输文件”</p> <h3>优点</h3> <ol> <li>不需要其他操作,简单方便</li> </ol> <h3>缺点</h3> <ol> <li>文件一多就会复制很慢很慢</li> </ol> <h2>方法二:使用第三方app直接传</h2> <h3>缺点</h3> <ol> <li>需要安装app</li> <li>传输慢</li> <li><code>android/data</code> 下的文件可能无法获取到并传输,如果再手机上把 <code>android/data</code>复制到 <code>Download</code>,可能无法复制目录中的文件</li> </ol> <h2>方法三:先再手机上使用第三方app压缩再传输</h2> <p>例如使用 <code>ES文件浏览器</code></p> <h3>优点</h3> <ol> <li>压缩后,传输会快很多</li> </ol> <h3>缺点</h3> <ol> <li>有些文件名中有特殊字符会导致压缩失败</li> </ol> <h2>方法三:使用adb pull 下载</h2> <p>需要在电脑中安装 <a href="https://zodream.cn/to/0_0/url/aHR0cHM6Ly9rd2MuY29jb21pLmV1Lm9yZy9odHRwczovL2RsLmdvb2dsZS5jb20vYW5kcm9pZC9yZXBvc2l0b3J5L3BsYXRmb3JtLXRvb2xzLWxhdGVzdC13aW5kb3dzLnppcA.html">adb</a></p> <p>使用USB连接电脑</p> <p>在手机上选择“USB用于传输文件”</p> <p>再手机 <code>设置</code> 中打开 <code>开发者模式</code>,开启 <code>USB调试功能</code></p> <p>电脑上打开终端命令行</p> <pre><code># adb pull &amp;lt;手机中的文件路径&amp;gt; &amp;lt;保存在电脑的位置&amp;gt; adb pull /storage/emulated/0/Android/data/com.xxx files</code></pre> <h3>优点</h3> <ol> <li>就算文件多,传输也很快</li> </ol> <h3>缺点</h3> <ol> <li>一些文件名中有特殊字符会导致传输中断,无法跳过,可能是 linux 和 windows 系统的原因导致的,电脑是 linux 系统的话不会出现这个问题</li> </ol> <h2>方法四【推荐】:使用adb压缩下载</h2> <p><strong>注意千万不要使用以下命令</strong></p> <p>这两种命令的原理就是压缩<code>/storage/emulated/0/Android/data/com.xx</code>不保存直接输出终端,再保存终端输出的内容到 <code>aaa.tar</code> 文件,但是会导致文件出现<code>0xFF 0xFE</code> 和 很多 <code>0x0</code>字节,导致解压缩软件无法读取</p> <pre><code>adb exec-out tar chf - -C /storage/emulated/0/Android/data/com.xx files &amp;gt; aaa.tar adb shell 'tar -cf - /storage/emulated/0/Android/data/xx 2&amp;gt;/dev/null' &amp;gt; backup.tar.gz</code></pre> <p><strong>可以使用先压缩到手机,再下载出来</strong></p> <pre><code># 压缩 /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</code></pre> <h3>tar 命令</h3> <pre><code>-c: 建立压缩档案 -x:解压 -t:查看内容 -r:向压缩归档文件末尾追加文件 -u:更新原压缩包中的文件 -f: 文件 -h: 解除软链接</code></pre> <p>所以 <code>tar chf &amp;lt;保存的文件&amp;gt; &amp;lt;要压缩的目录&amp;gt;</code> 就是 <code>tar -c &amp;lt;保存的文件&amp;gt; -h -f &amp;lt;要压缩的目录&amp;gt;</code> 的意思</p> <h3>优点</h3> <ol> <li>所有的文件都能传输</li> <li>在手机上压缩更快</li> </ol> <h3>缺点</h3> <ol> <li>需要手机空间大</li> </ol>

2024/8/4
阅读更多

AI学习笔记

<h2>分词原理</h2> <p>将每个字归类为: 词首(B)、词中(M)、词尾(E)、单子词(S)</p> <h2>文字生成原理</h2> <p>输入前一个字,输出后一个字;训练时就是将n个字符的内容 [0, n-1] 作为输入,将 [1, n] 做为预测值。</p>

2024/6/13
阅读更多

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

<h2>前言</h2> <p>以前使用 SiteServer CMS 6.8 在 Windows server 12 的服务器上部署了两个企业站,现在需要换到 Linux 服务器上。</p> <p>SiteServer CMS 6.8 使用的是 Asp.net 不支持Linux 系统,所以需要升级到 SiteServer CMS 7</p> <p>SiteServer CMS 7 使用的是 .NET Core 支持跨平台。</p> <p>这里干脆升级到最新的 SiteServer CMS 7.30 (.NET 8)</p> <h2>先决条件</h2> <p>Windows 服务器未过期或拥有Windows电脑并安装了 IIS + MySQL + Asp net core 运行时</p> <p><a href="https://zodream.cn/to/0_0/url/aHR0cHM6Ly9zc2Ntcy5jb20vZG9jcy92Ny9nZXR0aW5nLXN0YXJ0ZWQvdXNpbmctd2luZG93cy5odG1s.html">在Windows中运行SSCMS</a></p> <p><a href="https://zodream.cn/to/0_0/url/aHR0cHM6Ly9zc2Ntcy5jb20vZG9jcy92Ny9jbGkvYWR2YW5jZWQvb2xkZXItdmVyc2lvbi11cGdyYWRlLmh0bWw.html">SSCMS老版本升级</a></p> <h2>概念讲解</h2> <p>SSCMS 的模板语法不需要变动;</p> <p>主要是数据表的变动;</p> <h2>分步指导</h2> <p>假设旧版本的数据已经在MySQL数据库中 </p> <ol> <li>在Windows上部署 SSCMS 7.30</li> </ol> <p>从 <a href="https://zodream.cn/to/0_0/url/aHR0cHM6Ly9zc2Ntcy5jb20v.html">官网</a> 直接下最新的 windows 版本即可</p> <p>解压放到一个文件里,例如 <code>D:/sscms</code></p> <p>根据 “<a href="https://zodream.cn/to/0_0/url/aHR0cHM6Ly9zc2Ntcy5jb20vZG9jcy92Ny9jbGkvYWR2YW5jZWQvb2xkZXItdmVyc2lvbi11cGdyYWRlLmh0bWw.html">SSCMS老版本升级</a>” 升级文档进行操作即可</p> <ol start="2"> <li>更新数据表</li> </ol> <p>新建一个文件 <code>D:/sscms/old.json</code> </p> <pre><code class="language-json">{ "Database": { "Type": "MySql", "ConnectionString": "Server=127.0.0.1;Uid=root;Pwd=root;Database=sqldb_old;SslMode=none;CharSet=utf8" } }</code></pre> <p>在 <code>D:/sscms</code> 文件夹中打开终端 <code>PowerShell</code>,运行命令更新数据</p> <pre><code>.\sscms data backup -d backup -c old.json .\sscms data update -d backup</code></pre> <p>新建一个文件 <code>D:/sscms/sscms.json</code>, </p> <p><strong>注意</strong>:<code>sqldb_new</code> 数据库必须为空</p> <pre><code class="language-json">{ "Database": { "Type": "MySql", "ConnectionString": "Server=127.0.0.1;Uid=root;Pwd=root;Database=sqldb_new;SslMode=none;CharSet=utf8" } }</code></pre> <p>运行命令恢复数据</p> <pre><code>sscms data restore -d update</code></pre> <p>完成数据恢复后,需要在浏览器中进入 <code>http://&amp;lt;域名&amp;gt;/ss-admin/syncDatabase/</code> 数据库升级界面,点击升级按钮,完成数据库升级。</p> <p><strong>注意</strong>: <code>D:/sscms/sscms.json</code> 需要增加 <code>SecurityKey</code> 字段,不然后台登录验证码无法显示</p> <pre><code class="language-json">{ "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": [] } }</code></pre> <ol start="3"> <li>升级中的一些问题</li> </ol> <h3>后台登录验证码无法显示</h3> <p><code>D:/sscms/sscms.json</code> 需要增加 <code>SecurityKey</code> 字段,具体值需要新安装一个SSCMS系统(删除<code>D:/sscms/sscms.json</code>文件即可),复制新的 <code>sscms.json</code> 文件更改 <code>Database.ConnectionString</code> 值即可</p> <h3>老版本的密码不能用了</h3> <p>新安装一个SSCMS系统(删除<code>D:/sscms/sscms.json</code>文件即可),设置管理员密码,然后到数据库中 找到表 <code>siteserver_administrator</code> 复制 <code>Password</code>、 <code>PasswordSalt</code> 两个字段值即可。</p> <h3>后台登录提示字段不存在</h3> <p>需要手动复制新系统的数据表结构。因为SSCMS版本变动表结构也发生了变动。</p> <h3>登录进入显示需要创建站点</h3> <p>需要修改数据表 <code>siteserver_site</code> 的字段 <code>SiteType</code> 值为 <code>web</code></p> <h3>前台页面导航栏显示不全</h3> <p>需要到后台的<code>栏目管理</code>-&amp;gt;<code>编辑栏目</code>手动勾选 <code>栏目组</code> <code>导航</code>即可</p> <p><img src="https://zodream.cn/assets/upload/image/20240507/1715050357.jpg" title="Snipaste_2024-05-07_10-46-29.jpg" alt="Snipaste_2024-05-07_10-46-29.jpg" /></p> <h3>前台页面的包含文件不加载</h3> <p>需要到后台的<code>显示管理</code>-&amp;gt; <code>包含文件管理</code> 添加即可</p> <p><img src="https://zodream.cn/assets/upload/image/20240507/1715050377.jpg" title="Snipaste_2024-05-07_10-46-53.jpg" alt="Snipaste_2024-05-07_10-46-53.jpg" /></p> <ol start="4"> <li>在 linux 系统安装 netcore 环境</li> </ol> <p>这里使用的是阿里云ECS服务器</p> <p>使用命令 <code>lsb_release -a</code> 查询的系统信息为</p> <pre><code>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</code></pre> <p>所以安装 netcore 的方法参考:<a href="https://zodream.cn/to/0_0/url/aHR0cHM6Ly9zc2Ntcy5jb20vZG9jcy92Ny9nZXR0aW5nLXN0YXJ0ZWQvdXNpbmctbGludXgtcmhlbC5odG1s.html">在RHEL和CentOS Stream中运行SSCMS</a></p> <pre><code>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</code></pre> <ol start="5"> <li>配置站点</li> </ol> <p>假设已安装过 Nginx</p> <h3>a站点</h3> <p>nginx 配置</p> <pre><code>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; } }</code></pre> <p>a 站点的 <code>/data/a/sscms.json</code></p> <pre><code class="language-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": [] } }</code></pre> <p>创建 a 站点的服务文件 <code>/etc/systemd/system/a.service</code></p> <pre><code>[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</code></pre> <h2>b 站点</h2> <h3>a站点</h3> <p>nginx 配置</p> <pre><code>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; } }</code></pre> <p>b 站点的 <code>/data/b/sscms.json</code></p> <pre><code class="language-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": [] } }</code></pre> <p>创建 b 站点的服务文件 <code>/etc/systemd/system/b.service</code></p> <pre><code>[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</code></pre> <ol start="6"> <li>启动服务</li> </ol> <pre><code>sudo systemctl enable a.service sudo systemctl start a.service sudo systemctl status a.service # 验证服务是否启动 </code></pre> <p><img src="https://zodream.cn/assets/upload/image/20240507/1715050398.jpg" title="Snipaste_2024-05-07_10-43-12.jpg" alt="Snipaste_2024-05-07_10-43-12.jpg" /></p> <p>没有报错即配置正确,</p> <p>b站点服务同理</p> <ol start="7"> <li>导入数据</li> </ol> <p><strong>这里需要注意</strong></p> <p>在 Windows 中数据表表名都是小写,但是在Linux 环境下是区分大小写的,所以需要手动更改 <code>sql</code> 文件中的数据库表名,再导入</p> <p>例如 <code>siteserver_administratorsinroles</code> 需要改为 <code>siteserver_AdministratorsInRoles</code></p> <p>具体对应表需要重新再 linux 下安装即可</p> <ol start="8"> <li>导入模板文件、资源文件</li> </ol> <h2>总结</h2> <p>升级过程还是比较繁琐的,需要有耐心。</p>

2024/5/7
阅读更多

周报:没有干货

<p>本周没有什么干货分享。</p> <h2>折腾UI</h2> <p>不是专业美术,所以必须得积累点UI样式,不然用到时没有好的实现方案。</p> <ol> <li>会员页</li> </ol> <p><img src="https://zodream.cn/assets/upload/image/20240410/1712749839.webp" title="user space" alt="user space" /></p> <p>参考至X的页面,主要是给用户自己提供交流通道。</p> <ol start="2"> <li>账户注销页面</li> </ol> <p><img src="https://zodream.cn/assets/upload/image/20240410/1712750181.webp" title="setting" alt="setting" /></p> <p>参考至 winui 设置页面,基本上PC 基于winui 的应用都用这个样式。</p> <p><img src="https://zodream.cn/assets/upload/image/20240410/1712750190.webp" title="account cancel" alt="account cancel" /></p> <p>忘了参考哪里了,反正我的其他项目都用这个样式。</p> <ol start="3"> <li>消息页面</li> </ol> <p><img src="https://zodream.cn/assets/upload/image/20240410/1712750216.webp" title="message" alt="message" /></p> <p>参考至 哔哩哔哩 我的消息,风格类似。</p> <h2>看剧集</h2> <ol> <li>刷完美剧《群星 Constellation》</li> </ol> <p>平行宇宙</p> <ol start="2"> <li>刷完美剧《洛基 Loki》1、2季</li> </ol> <p>恶作剧之神从良登基。</p> <ol start="3"> <li>刷完日漫《间谍过家家 SPY×FAMILY》1、2季</li> </ol> <p>从小孩的视角看低幼的事情就是笑点。</p> <h2>看源码</h2> <p>看大神千行 C 代码实现 GPT 的 训练 过程。</p> <p><a href="https://zodream.cn/to/0_0/url/aHR0cHM6Ly9naXRodWIuY29tL2thcnBhdGh5L2xsbS5j.html">https://github.com/karpathy/llm.c</a></p> <p>没办法,AI是未来,所以必须得了解AI、使用AI、然后创作AI。</p>

2024/4/10
阅读更多

推荐订阅

Chen's Blog,分享安全领域的所思、所想、所学。

空鸣深语

无论你是游戏死忠,还是轻度的休闲玩家,在这里都能找到感兴趣的东西。