diff --git a/Assets/font.png b/Assets/font.png
new file mode 100644
index 0000000..b4ca210
Binary files /dev/null and b/Assets/font.png differ
diff --git a/Image2Display/Image2Display/Assets/Languages/en-US.axaml b/Image2Display/Image2Display/Assets/Languages/en-US.axaml
index 7ffdeda..d1005aa 100644
--- a/Image2Display/Image2Display/Assets/Languages/en-US.axaml
+++ b/Image2Display/Image2Display/Assets/Languages/en-US.axaml
@@ -193,6 +193,7 @@
Copy to Clipboard
Export File
Total Characters
+ Fail to load font file, format error.
Settings
General
diff --git a/Image2Display/Image2Display/Assets/Languages/zh-CN.axaml b/Image2Display/Image2Display/Assets/Languages/zh-CN.axaml
index 35843dd..3bcd31e 100644
--- a/Image2Display/Image2Display/Assets/Languages/zh-CN.axaml
+++ b/Image2Display/Image2Display/Assets/Languages/zh-CN.axaml
@@ -132,7 +132,7 @@
最低有效位(LSB)
成功
数据已导出。
- 导出失败
+ 失败
导出调色板
导出当前图片所使用的所有颜色
导出数据
@@ -196,6 +196,7 @@
复制到剪贴板
导出文件
总字符数量
+ 字体文件加载失败,文件格式不正确。
设置
diff --git a/Image2Display/Image2Display/Helpers/DemoFontData.cs b/Image2Display/Image2Display/Helpers/DemoFontData.cs
index cdffc6e..852fed5 100644
--- a/Image2Display/Image2Display/Helpers/DemoFontData.cs
+++ b/Image2Display/Image2Display/Helpers/DemoFontData.cs
@@ -8,6 +8,10 @@ namespace Image2Display.Helpers;
public class DemoFontData
{
+ public static byte[] Default
+ {
+ get => ZiSymbol;
+ }
public static readonly byte[] ZiSymbol = [
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x4B,0x48,0x06,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x88,0xFF,0xA2,0x00,
diff --git a/Image2Display/Image2Display/Helpers/FontConvert.cs b/Image2Display/Image2Display/Helpers/FontConvert.cs
index b43f056..1ddec5f 100644
--- a/Image2Display/Image2Display/Helpers/FontConvert.cs
+++ b/Image2Display/Image2Display/Helpers/FontConvert.cs
@@ -1,7 +1,10 @@
using Image2Display.Models;
+using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;
+using SkiaSharp;
using System;
using System.Collections.Generic;
+using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
@@ -18,7 +21,7 @@ public class FontConvert
/// 像素宽度
/// 图片缓冲区,没给的话会新建一个
/// 图片缓冲
- public static ImageData GetImage(byte[] data, int width, int height,ImageData? image = null)
+ public static ImageData GetImage(byte[] data, int width, int height, ImageData? image = null)
{
//像素尺寸
var pixelSize = 8;
@@ -65,5 +68,224 @@ public static ImageData GetImage(byte[] data, int width, int height,ImageData? i
return image;
}
+ public static byte[] GetData(SKTypeface font, int size, char c, int width, int height, int offsetx, int offsety)
+ {
+ //创建画布
+ using var surface = SKSurface.Create(new SKImageInfo(width, height));
+ //创建画笔
+ using var paint = new SKPaint
+ {
+ Color = SKColors.White,
+ TextSize = size,
+ Typeface = font,
+ TextAlign = SKTextAlign.Center,
+ IsAntialias = true,
+ };
+ //画布上画字
+ surface.Canvas.DrawText(c.ToString(), width/2 + offsetx, height - offsety, paint);
+ //获取像素数据
+ IntPtr data = surface.PeekPixels().GetPixels();
+ var result = new byte[width * height];
+
+ // 获取像素数据
+ using var pixmap = surface.PeekPixels();
+ if (pixmap != null)
+ {
+ // 遍历像素数据
+ for (int y = 0; y < pixmap.Height; y++)
+ {
+ for (int x = 0; x < pixmap.Width; x++)
+ {
+ IntPtr pixel = pixmap.GetPixels(x, y);
+ var buff = new byte[pixmap.BytesPerPixel];
+ System.Runtime.InteropServices.Marshal.Copy(pixel, buff, 0, buff.Length);
+ result[x + y * width] = buff[0];
+ }
+ }
+ }
+
+ return result;
+ }
+
+ ///
+ /// 数据二值化处理(仅用于预览)
+ ///
+ public static void ThresholdImage(byte[] data, byte threshold)
+ {
+ for (int i = 0; i < data.Length; i++)
+ {
+ data[i] = data[i] > threshold ? (byte)255 : (byte)0;
+ }
+ }
+
+ ///
+ /// 图片转换为特定灰度的图片(仅用于预览)
+ ///
+ public static void GrayScaleImage(byte[] data, int bit)
+ {
+ for (int i = 0; i < data.Length; i++)
+ {
+ //这个灰度位数,有多少种颜色
+ var number = 1 << bit;
+ //每个颜色的间隔
+ var interval = 255 / (number - 1);
+ //把每个格子的灰度对齐到最近的颜色
+ data[i] = (byte)((data[i] / interval) * interval);
+ }
+ }
+
+ ///
+ /// 反转图像颜色(仅用于预览)
+ ///
+ public static void InvertImage(byte[] data)
+ {
+ for (int i = 0; i < data.Length; i++)
+ {
+ data[i] = (byte)(255 - data[i]);
+ }
+ }
+
+ private static void IteratePixel(byte[] data, int rotate, int width, int height, Action action)
+ {
+ //主要开始1 主要结束1 次要开始2 次要结束2 是否先x后y
+ var (m1, m2, p1, p2, xFirst) = rotate switch
+ {
+ 0 => (0, width - 1, 0, height - 1, true),
+ 1 => (width - 1, 0, 0, height - 1, true),
+ 2 => (0, width - 1, height - 1, 0, true),
+ 3 => (width - 1, 0, height - 1, 0, true),
+ 4 => (0, height - 1, 0, width - 1, false),
+ 5 => (height - 1, 0, 0, width - 1, false),
+ 6 => (0, height - 1, width - 1, 0, false),
+ 7 => (height - 1, 0, width - 1, 0, false),
+ _ => throw new NotImplementedException(),
+ };
+
+ var mStep = m1 < m2 ? 1 : -1;
+ var pStep = p1 < p2 ? 1 : -1;
+ if (xFirst)
+ {
+ for (var y = p1; y != p2 + pStep; y += pStep)
+ {
+ for (var x = m1; x != m2 + mStep; x += mStep)
+ {
+ action(data[x + y * width]);
+ }
+ }
+ }
+ else
+ {
+ for (var x = p1; x != p2 + pStep; x += pStep)
+ {
+ for (var y = m1; y != m2 + mStep; y += mStep)
+ {
+ action(data[x + y * width]);
+ }
+ }
+ }
+ }
+
+ ///
+ /// 获取处理后的数据
+ ///
+ ///
+ public static List GetResultData(
+ byte[] raw, int width, int height,
+ bool isGray, int bit, byte threshold,
+ bool isInvert, int byteOrder, bool bitOrderMSB)
+ {
+ //先取反
+ if(isInvert)
+ InvertImage(raw);
+
+ var bitLength = 1;
+ if (isGray)
+ bitLength = bit;
+
+ var result = new List();
+ int bitIndex = 0;
+ byte lastByte = 0;
+ //按像素顺序遍历
+ IteratePixel(raw, byteOrder, width, height, (b) =>
+ {
+ if(isGray)
+ {
+ //灰度处理
+ b = (byte)(b >> (8 - bitLength));
+ }
+ else
+ {
+ //二值化处理
+ b = b > threshold ? (byte)1 : (byte)0;
+ }
+ //反向数据
+ if (!bitOrderMSB)
+ {
+ var temp = (byte)0;
+ for (int i = 0; i < bitLength; i++)
+ {
+ temp <<= 1;
+ temp |= (byte)(b & 1);
+ b >>= 1;
+ }
+ b = temp;
+ }
+ //添加到结果
+ lastByte <<= bitLength;
+ lastByte |= b;
+ bitIndex += bitLength;
+ if (bitIndex >= 8)
+ {
+ result.Add(lastByte);
+ lastByte = 0;
+ bitIndex = 0;
+ }
+ });
+ //最后一个字节
+ if (bitIndex > 0)
+ {
+ lastByte <<= 8 - bitIndex;
+ result.Add(lastByte);
+ }
+ return result;
+ }
+
+ ///
+ /// 将字体数据转换为C数组
+ ///
+ /// 数据,每个字一个数组
+ /// 字库宽度
+ /// 字库高度
+ /// 实际用的字符集
+ ///
+ public static string ByteListToCArray(List> data, int width, int height, IList charset)
+ {
+ var sb = new StringBuilder();
+ if (Utils.Settings.Language.Contains("ZH", StringComparison.CurrentCultureIgnoreCase))
+ sb.Append("/*@注意:此文件由Image2Display生成 */\n");
+ else
+ sb.Append("/*@Notice: This file is generated by Image2Display */\n");
+ if (data.Count == 0 || data[0].Count == 0 || charset.Count == 0)
+ sb.Append("/* no data */\n");
+ else
+ {
+ sb.Append($"/*@Size: {width}x{height}, " +
+ $"Char: {charset.Count}," +
+ $"Data per char: {data[0].Count} */\n");
+ }
+ sb.Append("const uint8_t fonts[] = {\n");
+ //每行一个字
+ for (int i = 0; i < charset.Count; i++)
+ {
+ sb.Append($"/* {charset[i]} */\n");
+ foreach (var b in data[i])
+ {
+ sb.Append($"0x{b:X2},");
+ }
+ sb.Append("\n\n");
+ }
+ sb.Append("};\n");
+ return sb.ToString();
+ }
}
diff --git a/Image2Display/Image2Display/ViewModels/FontConvertViewModel.cs b/Image2Display/Image2Display/ViewModels/FontConvertViewModel.cs
index 3079c86..f9bac7e 100644
--- a/Image2Display/Image2Display/ViewModels/FontConvertViewModel.cs
+++ b/Image2Display/Image2Display/ViewModels/FontConvertViewModel.cs
@@ -1,9 +1,11 @@
using Avalonia.Controls;
using Avalonia.Media.Imaging;
+using Avalonia.Platform.Storage;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using Image2Display.Helpers;
using Image2Display.Models;
+using SkiaSharp;
using System;
using System.Collections.Generic;
using System.IO;
@@ -20,6 +22,8 @@ public partial class FontConvertViewModel : ViewModelBase
[ObservableProperty]
private bool _UseSystemFont = true;
+ [ObservableProperty]
+ private bool _EnableUseSystemFontCheckBox = true;
[ObservableProperty]
private List _SystemFontList = new();
@@ -55,7 +59,7 @@ public partial class FontConvertViewModel : ViewModelBase
//字体配置
[ObservableProperty]
- private int _FontSize = 12;
+ private int _FontSize = 50;
[ObservableProperty]
private bool _FontBold = false;
[ObservableProperty]
@@ -67,9 +71,9 @@ public partial class FontConvertViewModel : ViewModelBase
//字模尺寸
[ObservableProperty]
- private int _FontWidth = 20;
+ private int _FontWidth = 50;
[ObservableProperty]
- private int _FontHeight = 20;
+ private int _FontHeight = 50;
[ObservableProperty]
private int _FontXOffset = 0;
[ObservableProperty]
@@ -79,7 +83,7 @@ public partial class FontConvertViewModel : ViewModelBase
[ObservableProperty]
private bool _UseThreshold = true;
[ObservableProperty]
- private int _Threshold = 128;
+ private byte _Threshold = 128;
//灰度位数 0~2: 2、4、8
[ObservableProperty]
private int _GrayBitIndex = 0;
@@ -96,6 +100,25 @@ public partial class FontConvertViewModel : ViewModelBase
[ObservableProperty]
private bool _RLECompress = false;
+ //提示文本
+ [ObservableProperty]
+ private bool _IsShowSuccess = false;
+ [ObservableProperty]
+ private bool _IsShowFail = false;
+ [ObservableProperty]
+ private string _FailMessage = "";
+
+ private void ShowSuccess()
+ {
+ IsShowSuccess = true;
+ IsShowFail = false;
+ }
+ private void ShowFail(string message)
+ {
+ IsShowSuccess = false;
+ IsShowFail = true;
+ FailMessage = message;
+ }
[ObservableProperty]
private string _PreviewText = Utils.GetI18n("PreviewText");
@@ -109,8 +132,9 @@ public partial class FontConvertViewModel : ViewModelBase
public FontConvertViewModel()
{
//初始化图片成格子图
- var pic = FontConvert.GetImage(DemoFontData.ASymbol, 20, 20, ImageCache);
- OriginalImage = new Bitmap(pic.GetStream());
+ var pic = FontConvert.GetImage(DemoFontData.Default, 20, 20, ImageCache);
+ using var ps = pic.GetStream();
+ OriginalImage = new Bitmap(ps);
//用skia接口获取系统字体列表
var fontMgr = SkiaSharp.SKFontManager.Default;
@@ -132,11 +156,18 @@ public FontConvertViewModel()
};
SystemFontList.Add(item);
}
-
+ if(SystemFontList.Count > 0)
+ ReloadSKTypeface();
+ else
+ {
+ UseSystemFont = false;
+ EnableUseSystemFontCheckBox = false;
+ }
+ /*****************************************/
//字符集需要刷新的情况
string[] charsetChangedElements =
[
- nameof(CharsetUppercaseLetters),
+ nameof(CharsetUppercaseLetters),
nameof(CharsetLowercaseLetters),
nameof(CharsetNumbers),
nameof(CharsetPunctuation),
@@ -146,17 +177,54 @@ public FontConvertViewModel()
nameof(CharsetGB2312),
nameof(CustomCharset)
];
+ //字符预览需要刷新的情况
+ string[] previewChangedElements =
+ [
+ nameof(UseSystemFont),
+ nameof(SelectedFontIndex),
+ nameof(FontName),
+ nameof(FontSize),
+ nameof(FontBold),
+ nameof(FontItalic),
+ nameof(FontUnderline),
+ nameof(FontStrikeout),
+ nameof(FontWidth),
+ nameof(FontHeight),
+ nameof(FontXOffset),
+ nameof(FontYOffset),
+ nameof(UseThreshold),
+ nameof(Threshold),
+ nameof(GrayBitIndex),
+ nameof(Invert),
+ ];
+ //字体需要刷新的情况
+ string[] fontChangedElements =
+ [
+ nameof(UseSystemFont),
+ nameof(SelectedFontIndex),
+ nameof(FontBold),
+ nameof(FontItalic),
+ nameof(FontUnderline),
+ nameof(FontStrikeout),
+ ];
PropertyChanged += async (sender, e) =>
{
//某个变量被更改
var name = e.PropertyName;
+ //更新字体
+ if (fontChangedElements.Contains(name))
+ ReloadSKTypeface();
+ //刷新字符集
+ if (previewChangedElements.Contains(name))
+ await RefreshPreview();
+ //刷新预览
if (charsetChangedElements.Contains(name))
{
await RefreshCharset();
+ await RefreshPreview();
}
-
};
}
@@ -168,6 +236,9 @@ private async Task RefreshCharset()
await Task.Run(() =>
{
chars.Clear();
+
+ if (!string.IsNullOrEmpty(CustomCharset))
+ chars.AddRange(CustomCharset);
if (CharsetUppercaseLetters)
chars.AddRange(Charset.GetUppercaseLetters());
if (CharsetLowercaseLetters)
@@ -184,47 +255,228 @@ await Task.Run(() =>
chars.AddRange(Charset.GetSecondLevelChinese());
if (CharsetGB2312)
chars.AddRange(Charset.GetGB2312());
- if (!string.IsNullOrEmpty(CustomCharset))
- chars.AddRange(CustomCharset);
//去除重复字符
chars = chars.Distinct().ToList();
//刷新字符数量
TotalCharacters = chars.Count;
+ //预览索引重置
+ PreviewIndex = 0;
+ });
+ }
+
+ private int PreviewIndex = 0;
+ private async Task RefreshPreview()
+ {
+ await Task.Run(() =>
+ {
+ //如果没有字符,显示默认字符
+ if (chars.Count == 0)
+ {
+ var pic = FontConvert.GetImage(DemoFontData.Default, 20, 20, ImageCache);
+ using var ps = pic.GetStream();
+ OriginalImage = new Bitmap(ps);
+ return;
+ }
+
+ if (LoadedFont == null)
+ return;
+
+ //获取原始图像数据
+ var data = FontConvert.GetData(LoadedFont,
+ FontSize, chars[PreviewIndex],
+ FontWidth, FontHeight,
+ FontXOffset, FontYOffset);
+
+ // 各种处理
+ if (UseThreshold)
+ {
+ FontConvert.ThresholdImage(data, Threshold);
+ }
+ else
+ {
+ var grayBit = GrayBitIndex switch
+ {
+ 0 => 2,
+ 1 => 4,
+ 2 => 8,
+ _ => 2
+ };
+ FontConvert.GrayScaleImage(data, grayBit);
+ }
+ if(Invert)
+ FontConvert.InvertImage(data);
+
+ var fp = FontConvert.GetImage(data, FontWidth, FontHeight, ImageCache);
+ using var fps = fp.GetStream();
+ OriginalImage = new Bitmap(fps);
});
}
[RelayCommand]
private async Task ShowPreviousCharacter()
{
- //TODO 显示上一个字符
+ if (chars.Count == 0)
+ return;
+ PreviewIndex -= 1;
+ if (PreviewIndex < 0)
+ PreviewIndex = chars.Count - 1;
+ await RefreshPreview();
}
[RelayCommand]
private async Task ShowNextCharacter()
{
- //TODO 显示下一个字符
+ if (chars.Count == 0)
+ return;
+ PreviewIndex += 1;
+ if (PreviewIndex >= chars.Count)
+ PreviewIndex = 0;
+ await RefreshPreview();
}
[RelayCommand]
private async Task ShowRandomCharacter()
{
- //TODO 随机显示一个字符
+ if (chars.Count == 0)
+ return;
+ PreviewIndex = new Random().Next(0, chars.Count);
+ await RefreshPreview();
}
+ private SKTypeface? LoadedFont = null;
+ private string? LastFontPath = null;
[RelayCommand]
private async Task LoadFontFile()
{
- //TODO 加载字体文件
+ var fileType = new FilePickerFileType("Font")
+ {
+ Patterns = ["*.ttf","*.otf"],
+ AppleUniformTypeIdentifiers = ["public.item"],
+ MimeTypes = ["font/ttf", "font/otf"]
+ };
+ var files = await DialogHelper.ShowOpenFileDialogAsync(fileType, false);
+ if (files.Count == 0)
+ return;
+ var filePath = files[0].Path.LocalPath;
+
+ //尝试加载字体文件,失败则提示
+ if(!ReloadSKTypeface(filePath))
+ {
+ ShowFail(Utils.GetI18n("LoadFontFileFail"));
+ return;
+ }
+
+ FontName = Path.GetFileName(filePath);
+ LastFontPath = filePath;
+ await RefreshPreview();
+ }
+
+ private bool ReloadSKTypeface(string? path = null)
+ {
+ if (UseSystemFont)
+ {
+ if (SystemFontList.Count == 0)
+ return false;
+ var fontName = SystemFontList[SelectedFontIndex].Tag as string;
+ LoadedFont = SKTypeface.FromFamilyName(fontName,
+ FontBold ? SKFontStyleWeight.Bold : SKFontStyleWeight.Normal,
+ SKFontStyleWidth.Normal,
+ FontItalic ? SKFontStyleSlant.Italic : SKFontStyleSlant.Upright);
+ }
+ else
+ {
+ path ??= LastFontPath;
+ if (string.IsNullOrEmpty(path))
+ return false;
+ var font = SKTypeface.FromFile(path);
+ if (font == null)
+ return false;
+ LoadedFont = font;
+ }
+ return true;
+ }
+
+
+ private async Task GetFontData()
+ {
+ if(LoadedFont == null)
+ {
+ ShowFail("no font");
+ return string.Empty;
+ }
+ var result = string.Empty;
+ var error = string.Empty;
+ await Task.Run(() =>
+ {
+ var data = new List>();
+ foreach (var c in chars)
+ {
+ var raw = FontConvert.GetData(LoadedFont,
+ FontSize, c,
+ FontWidth, FontHeight,
+ FontXOffset, FontYOffset);
+
+ var grayBit = GrayBitIndex switch
+ {
+ 0 => 2,
+ 1 => 4,
+ 2 => 8,
+ _ => 2
+ };
+ var d = FontConvert.GetResultData(raw, FontWidth, FontHeight,
+ !UseThreshold, grayBit, Threshold,
+ Invert, ByteOrderIndex, BitOrderMSB);
+ data.Add(d);
+ }
+ result = FontConvert.ByteListToCArray(data, FontWidth, FontHeight, chars);
+ });
+ if (!string.IsNullOrEmpty(error))
+ {
+ ShowFail(error);
+ return string.Empty;
+ }
+ return result;
}
[RelayCommand]
private async Task CopyFontCode()
{
- //TODO 复制字体代码
+ var data = await GetFontData();
+ if(string.IsNullOrEmpty(data))
+ return;
+ if (await Utils.CopyString(data))
+ {
+ ShowSuccess();
+ }
+ else
+ {
+ ShowFail("Copy failed");
+ }
}
+ public static FilePickerFileType CFiles { get; } = new("C Files")
+ {
+ Patterns = ["*.c"],
+ AppleUniformTypeIdentifiers = ["public.source-code"],
+ MimeTypes = ["text/x-csrc"]
+ };
[RelayCommand]
private async Task SaveFontFile()
{
- //TODO 保存字体文件
+ //保存数据
+ var path = await DialogHelper.ShowSaveFileDialogAsync("font_data", CFiles);
+ if (path == null)
+ return;
+ var data = await GetFontData();
+ if (string.IsNullOrEmpty(data))
+ return;
+ try
+ {
+ await File.WriteAllTextAsync(path, data);
+ ShowSuccess();
+ }
+ catch (Exception e)
+ {
+ ShowFail("Save failed: " + e.Message);
+ }
}
}
}
diff --git a/Image2Display/Image2Display/Views/FontConvertView.axaml b/Image2Display/Image2Display/Views/FontConvertView.axaml
index f7d0ab2..c6a34eb 100644
--- a/Image2Display/Image2Display/Views/FontConvertView.axaml
+++ b/Image2Display/Image2Display/Views/FontConvertView.axaml
@@ -20,7 +20,7 @@
Margin="10"
BorderBrush="{DynamicResource SurfaceStrokeColorDefaultBrush}"
BorderThickness="2">
-
+
@@ -62,16 +62,17 @@
+ IsChecked="{Binding UseSystemFont}"
+ IsEnabled="{Binding EnableUseSystemFontCheckBox}" />
-
+