diff --git a/tools/ico_extractor/IconExtractor.cs b/tools/ico_extractor/IconExtractor.cs new file mode 100644 index 0000000..21b4624 --- /dev/null +++ b/tools/ico_extractor/IconExtractor.cs @@ -0,0 +1,104 @@ +using System; +using System.Drawing; +using System.Runtime.InteropServices; +using System.IO; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; + +namespace AppStore.Tools.IconExtractor +{ + public static class IconExtractor + { + /// + /// 获取ICO文件中的实际尺寸列表 + /// + public static List GetIconDimensions(string filePath) + { + var sizes = new List(); + try + { + using (var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read)) + using (var reader = new BinaryReader(fs)) + { + // 读取ICO文件头 + reader.ReadUInt16(); // 保留字段 + ushort type = reader.ReadUInt16(); // 1=ICO, 2=CUR + ushort count = reader.ReadUInt16(); // 图像数量 + + if (type != 1) return sizes; // 不是ICO文件 + + // 读取每个图像目录项 + for (int i = 0; i < count; i++) + { + byte width = reader.ReadByte(); + byte height = reader.ReadByte(); + reader.ReadBytes(14); // 跳过其他字段 + + // 宽度/高度为0表示256像素 + int size = width == 0 ? 256 : width; + sizes.Add(size); + } + } + } + catch + { + // 忽略所有错误,返回空列表 + } + return sizes; + } + [DllImport("shell32.dll", CharSet = CharSet.Auto)] + private static extern int ExtractIconEx( + string lpszFile, + int nIconIndex, + [AllowNull] IntPtr[] phiconLarge, + [AllowNull] IntPtr[] phiconSmall, + int nIcons); + + [DllImport("user32.dll", CharSet = CharSet.Auto)] + private static extern bool DestroyIcon(IntPtr handle); + + /// + /// 从文件中提取图标 + /// + /// 文件路径 + /// 图标索引 + /// 是否提取大图标 + /// 提取的图标 + public static Icon ExtractIconFromFile(string filePath, int iconIndex = 0) + { + IntPtr[] hIcons = new IntPtr[1]; + int extractedCount = ExtractIconEx(filePath, iconIndex, hIcons, null, 1); + + if (extractedCount <= 0 || hIcons[0] == IntPtr.Zero) + throw new FileNotFoundException("无法从文件中提取图标"); + + // 直接返回原始图标 + Icon icon = (Icon)Icon.FromHandle(hIcons[0]).Clone(); + DestroyIcon(hIcons[0]); + return icon; + } + + /// + /// 将图标保存为文件 + /// + /// 图标对象 + /// 输出路径 + public static void SaveIconToFile(Icon icon, string outputPath) + { + using (FileStream fs = new FileStream(outputPath, FileMode.Create)) + { + icon.Save(fs); + } + } + + /// + /// 获取文件中的图标数量 + /// + /// 文件路径 + /// 图标数量 + public static int GetIconCount(string filePath) + { + return ExtractIconEx(filePath, -1, null, null, 0); + } + } +} diff --git a/tools/ico_extractor/IconExtractorConstants.cs b/tools/ico_extractor/IconExtractorConstants.cs new file mode 100644 index 0000000..f5def1d --- /dev/null +++ b/tools/ico_extractor/IconExtractorConstants.cs @@ -0,0 +1,19 @@ +namespace AppStore.Tools.IconExtractor +{ + public static class IconExtractorConstants + { + public const string FileFilter = "可执行文件|*.exe;*.dll;*.ocx;*.cpl|所有文件|*.*"; + public const string SaveFilter = "图标文件|*.ico|位图文件|*.bmp|PNG文件|*.png"; + + public static readonly int[] SupportedSizes = { 16, 24, 32, 48, 64, 128, 256, 512 }; + public static readonly int DefaultExtractSize = 256; + + public const string ErrorNoIconsFound = "文件不包含任何图标"; + public const string ErrorExtractionFailed = "图标提取失败"; + public const string ErrorInvalidIndex = "无效的图标索引"; + public const string ErrorFileNotFound = "文件未找到"; + + public const int MaxRecentFiles = 5; + public const int DefaultPreviewSize = 128; + } +} diff --git a/tools/ico_extractor/IconExtractorForm.cs b/tools/ico_extractor/IconExtractorForm.cs new file mode 100644 index 0000000..0b5dcc7 --- /dev/null +++ b/tools/ico_extractor/IconExtractorForm.cs @@ -0,0 +1,186 @@ +using System; +using System.Drawing; +using System.Windows.Forms; +using System.IO; +using AppStore.Tools.IconExtractor; + +namespace AppStore.Tools.IconExtractor +{ + public class IconExtractorForm : Form + { + private Button btnBrowse = new Button(); + private Button btnSave = new Button(); + private NumericUpDown numIconIndex = new NumericUpDown(); + private PictureBox picIcon = new PictureBox(); + private Label lblStatus = new Label(); + private TextBox txtFilePath = new TextBox(); + private ComboBox cmbIconSize = new ComboBox(); + private string currentFilePath = string.Empty; + + public IconExtractorForm() + { + this.Text = "图标提取器"; + this.Size = new Size(500, 550); + this.StartPosition = FormStartPosition.CenterScreen; + InitializeComponents(); + } + + private void InitializeComponents() + { + // 文件路径文本框 + txtFilePath.Location = new Point(20, 20); + txtFilePath.Size = new Size(300, 25); + txtFilePath.ReadOnly = true; + this.Controls.Add(txtFilePath); + + // 浏览按钮 + btnBrowse.Text = "浏览..."; + btnBrowse.Location = new Point(330, 20); + btnBrowse.Size = new Size(80, 25); + btnBrowse.Click += BtnBrowse_Click; + this.Controls.Add(btnBrowse); + + // 图标索引标签 + Label lblIndex = new Label(); + lblIndex.Text = "图标索引:"; + lblIndex.Location = new Point(20, 60); + lblIndex.Size = new Size(80, 20); + this.Controls.Add(lblIndex); + + // 图标索引选择器 + numIconIndex.Location = new Point(100, 60); + numIconIndex.Size = new Size(80, 20); + numIconIndex.Minimum = 0; + numIconIndex.ValueChanged += NumIconIndex_ValueChanged; + this.Controls.Add(numIconIndex); + + // 图标预览区域 + picIcon.Location = new Point(20, 100); + picIcon.Size = new Size(256, 256); + picIcon.SizeMode = PictureBoxSizeMode.Zoom; + picIcon.BorderStyle = BorderStyle.FixedSingle; + this.Controls.Add(picIcon); + + // 索引说明 + Label lblIndexHelp = new Label(); + lblIndexHelp.Text = "索引号表示文件中的第几个图标,从0开始"; + lblIndexHelp.Location = new Point(20, 240); + lblIndexHelp.Size = new Size(300, 20); + this.Controls.Add(lblIndexHelp); + + // 保存按钮 + btnSave.Text = "保存图标"; + btnSave.Location = new Point(20, 450); + btnSave.Size = new Size(100, 30); + btnSave.Click += BtnSave_Click; + this.Controls.Add(btnSave); + + // 状态标签 + lblStatus.Location = new Point(20, 490); + lblStatus.Size = new Size(400, 20); + lblStatus.Text = "请选择包含图标的文件"; + this.Controls.Add(lblStatus); + } + + private void BtnBrowse_Click(object sender, EventArgs e) + { + using (OpenFileDialog ofd = new OpenFileDialog()) + { + ofd.Filter = "可执行文件|*.exe;*.dll;*.ocx;*.cpl|所有文件|*.*"; + if (ofd.ShowDialog() == DialogResult.OK) + { + currentFilePath = ofd.FileName; + txtFilePath.Text = currentFilePath; + LoadIconInfo(); + } + } + } + + private void LoadIconInfo() + { + try + { + int iconCount = IconExtractor.GetIconCount(currentFilePath); + numIconIndex.Maximum = Math.Max(0, iconCount - 1); + + // 如果是ICO文件,获取实际包含的尺寸 + if (currentFilePath.EndsWith(".ico", StringComparison.OrdinalIgnoreCase)) + { + var sizes = IconExtractor.GetIconDimensions(currentFilePath); + if (sizes.Count > 0) + { + cmbIconSize.Items.Clear(); + foreach (var size in sizes) + { + cmbIconSize.Items.Add($"{size}x{size}"); + } + + // 默认选择最接近256的尺寸 + int closest = sizes.OrderBy(s => Math.Abs(s - 256)).First(); + cmbIconSize.SelectedIndex = sizes.IndexOf(closest); + } + } + + lblStatus.Text = $"找到 {iconCount} 个图标"; + ExtractAndDisplayIcon(); + } + catch (Exception ex) + { + lblStatus.Text = $"错误: {ex.Message}"; + picIcon.Image = null; + } + } + + private void NumIconIndex_ValueChanged(object sender, EventArgs e) + { + if (!string.IsNullOrEmpty(currentFilePath)) + { + ExtractAndDisplayIcon(); + } + } + + private void ExtractAndDisplayIcon() + { + try + { + Icon icon = IconExtractor.ExtractIconFromFile(currentFilePath, (int)numIconIndex.Value); + picIcon.Image = icon.ToBitmap(); + lblStatus.Text = $"显示原始图标 #{numIconIndex.Value}"; + } + catch (Exception ex) + { + lblStatus.Text = $"提取图标失败: {ex.Message}"; + picIcon.Image = null; + } + } + + private void BtnSave_Click(object sender, EventArgs e) + { + if (picIcon.Image == null) + { + MessageBox.Show("没有可保存的图标", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information); + return; + } + + using (SaveFileDialog sfd = new SaveFileDialog()) + { + sfd.Filter = "图标文件|*.ico|位图文件|*.bmp|PNG文件|*.png"; + if (sfd.ShowDialog() == DialogResult.OK) + { + try + { + using (Icon icon = IconExtractor.ExtractIconFromFile(currentFilePath, (int)numIconIndex.Value)) + { + IconExtractor.SaveIconToFile(icon, sfd.FileName); + lblStatus.Text = $"已保存原始图标到 {sfd.FileName}"; + } + } + catch (Exception ex) + { + MessageBox.Show($"保存图标失败: {ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + } + } + } + } +} diff --git a/tools/ico_extractor/IconExtractorHelper.cs b/tools/ico_extractor/IconExtractorHelper.cs new file mode 100644 index 0000000..9e3bfb7 --- /dev/null +++ b/tools/ico_extractor/IconExtractorHelper.cs @@ -0,0 +1,73 @@ +using System; +using System.Drawing; +using System.IO; +using System.Collections.Generic; + +namespace AppStore.Tools.IconExtractor +{ + public static class IconExtractorHelper + { + /// + /// 将图标转换为位图 + /// + public static Bitmap ConvertIconToBitmap(Icon icon, Size size) + { + Bitmap bitmap = new Bitmap(size.Width, size.Height); + using (Graphics g = Graphics.FromImage(bitmap)) + { + g.DrawIcon(icon, new Rectangle(0, 0, size.Width, size.Height)); + } + return bitmap; + } + + /// + /// 批量提取文件中的所有图标 + /// + public static List ExtractAllIcons(string filePath) + { + List icons = new List(); + int count = IconExtractor.GetIconCount(filePath); + + for (int i = 0; i < count; i++) + { + try + { + Icon icon = IconExtractor.ExtractIconFromFile(filePath, i); + icons.Add(icon); + } + catch + { + // 忽略提取失败的图标 + } + } + + return icons; + } + + /// + /// 将图标保存为PNG格式 + /// + public static void SaveIconAsPng(Icon icon, string outputPath) + { + using (Bitmap bitmap = icon.ToBitmap()) + { + bitmap.Save(outputPath, System.Drawing.Imaging.ImageFormat.Png); + } + } + + /// + /// 检查文件是否包含图标 + /// + public static bool HasIcons(string filePath) + { + try + { + return IconExtractor.GetIconCount(filePath) > 0; + } + catch + { + return false; + } + } + } +} diff --git a/tools/ico_extractor/IconExtractorSettings.cs b/tools/ico_extractor/IconExtractorSettings.cs new file mode 100644 index 0000000..8fb9c52 --- /dev/null +++ b/tools/ico_extractor/IconExtractorSettings.cs @@ -0,0 +1,123 @@ +using System; +using System.IO; +using System.Text.Json; + +namespace AppStore.Tools.IconExtractor +{ + public static class IconExtractorSettings + { + private static readonly string SettingsPath = Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), + "kortapp-z", "icon_extractor_settings.json"); + + public static SettingsData CurrentSettings { get; private set; } = new SettingsData(); + + public class SettingsData + { + public string LastUsedDirectory { get; set; } = string.Empty; + public string DefaultSaveFormat { get; set; } = "ico"; + public int DefaultIconSize { get; set; } = 128; + public string[] RecentFiles { get; set; } = Array.Empty(); + } + + /// + /// 加载设置 + /// + public static void LoadSettings() + { + try + { + if (File.Exists(SettingsPath)) + { + string json = File.ReadAllText(SettingsPath); + if (!string.IsNullOrWhiteSpace(json)) + { + try + { + using (JsonDocument doc = JsonDocument.Parse(json)) + { + var root = doc.RootElement; + CurrentSettings = new SettingsData + { + LastUsedDirectory = root.TryGetProperty("LastUsedDirectory", out var dir) ? dir.GetString() ?? string.Empty : string.Empty, + DefaultSaveFormat = root.TryGetProperty("DefaultSaveFormat", out var format) ? format.GetString() ?? "ico" : "ico", + DefaultIconSize = root.TryGetProperty("DefaultIconSize", out var size) ? size.GetInt32() : 128, + RecentFiles = root.TryGetProperty("RecentFiles", out var files) ? + JsonSerializer.Deserialize(files.GetRawText()) ?? Array.Empty() : + Array.Empty() + }; + } + } + catch + { + CurrentSettings = new SettingsData(); + } + } + } + } + catch + { + // 加载失败时使用默认设置 + CurrentSettings = new SettingsData(); + } + } + + /// + /// 保存设置 + /// + public static void SaveSettings() + { + try + { + if (string.IsNullOrEmpty(SettingsPath)) + return; + + string directory = Path.GetDirectoryName(SettingsPath); + if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory)) + { + Directory.CreateDirectory(directory); + } + + string json = JsonSerializer.Serialize(CurrentSettings); + File.WriteAllText(SettingsPath, json); + } + catch + { + // 忽略保存错误 + } + } + + /// + /// 添加最近使用的文件 + /// + public static void AddRecentFile(string filePath) + { + if (CurrentSettings.RecentFiles.Length >= 5) + { + var list = new List(CurrentSettings.RecentFiles); + list.RemoveAt(0); + CurrentSettings.RecentFiles = list.ToArray(); + } + + var newList = new List(CurrentSettings.RecentFiles); + if (!newList.Contains(filePath)) + { + newList.Add(filePath); + CurrentSettings.RecentFiles = newList.ToArray(); + SaveSettings(); + } + } + + /// + /// 更新最后使用的目录 + /// + public static void UpdateLastUsedDirectory(string directory) + { + if (Directory.Exists(directory) && CurrentSettings.LastUsedDirectory != directory) + { + CurrentSettings.LastUsedDirectory = directory; + SaveSettings(); + } + } + } +} diff --git a/tools/ico_extractor/IconExtractorToolCard.cs b/tools/ico_extractor/IconExtractorToolCard.cs new file mode 100644 index 0000000..1e85f0b --- /dev/null +++ b/tools/ico_extractor/IconExtractorToolCard.cs @@ -0,0 +1,39 @@ +using System; +using System.Drawing; +using System.Windows.Forms; +using System.IO; + +namespace AppStore.Tools.IconExtractor +{ + public class IconExtractorToolCard : ToolCard + { + public IconExtractorToolCard() + { + ToolName = "图标提取器"; + try + { + string iconPath = Path.Combine(Application.StartupPath, "img", "resource", "png", "QRcode.png"); + if (File.Exists(iconPath)) + { + ToolIcon = Image.FromFile(iconPath); + } + else + { + ToolIcon = SystemIcons.Application.ToBitmap(); + } + } + catch + { + ToolIcon = SystemIcons.Application.ToBitmap(); + } + this.ToolCardClicked += OnIconExtractorCardClicked; + UpdateDisplay(); + } + + private void OnIconExtractorCardClicked(object sender, EventArgs e) + { + var iconExtractorForm = new IconExtractorForm(); + iconExtractorForm.ShowDialog(); + } + } +}