diff --git a/HFUTCourseSimulation/Dialog/Simulator.xaml.cs b/HFUTCourseSimulation/Dialog/Simulator.xaml.cs index 8e89aaf..1ce7033 100644 --- a/HFUTCourseSimulation/Dialog/Simulator.xaml.cs +++ b/HFUTCourseSimulation/Dialog/Simulator.xaml.cs @@ -18,32 +18,6 @@ namespace HFUTCourseSimulation.Dialog { /// Interaction logic for Simulator.xaml /// public partial class Simulator : Window { - private static readonly string[] WEEK_NAMES = new string[] { - "星期一", - "星期二", - "星期三", - "星期四", - "星期五", - "星期六", - "星期日" - }; - - private static readonly ColorPair HEADBAR_COLOR = Util.ColorPreset.MdColors.Grey; - private static ColorPair GetSidebarColor(Kernel.Data.Presentation.IndexKind kind) { - switch (kind) { - case Kernel.Data.Presentation.IndexKind.Dawn: - return Util.ColorPreset.MdColors.Cyan; - case Kernel.Data.Presentation.IndexKind.Morning: - return Util.ColorPreset.MdColors.LightBlue; - case Kernel.Data.Presentation.IndexKind.Afternoon: - return Util.ColorPreset.MdColors.Amber; - case Kernel.Data.Presentation.IndexKind.Night: - return Util.ColorPreset.MdColors.BlueGrey; - default: - return Util.ColorPreset.MdColors.Grey; - } - } - public Simulator() { InitializeComponent(); @@ -62,7 +36,7 @@ namespace HFUTCourseSimulation.Dialog { // Initialize UI layout by semester // Setup grid rows and columns uiCoreGrid.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(1, GridUnitType.Auto) }); - for (int i = 0; i < WEEK_NAMES.Length; ++i) { + for (int i = 0; i < WeekNames.Length; ++i) { uiCoreGrid.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(1, GridUnitType.Star) }); } uiCoreGrid.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1, GridUnitType.Auto) }); @@ -72,17 +46,17 @@ namespace HFUTCourseSimulation.Dialog { } // Set head bar var headbar = new Rectangle(); - headbar.Fill = new SolidColorBrush(HEADBAR_COLOR.Background); + headbar.Fill = new SolidColorBrush(ColorConsistency.HeadbarColor.Background); Grid.SetRow(headbar, 0); Grid.SetRowSpan(headbar, 2); Grid.SetColumn(headbar, 0); - Grid.SetColumnSpan(headbar, WEEK_NAMES.Length + 1); + Grid.SetColumnSpan(headbar, WeekNames.Length + 1); uiCoreGrid.Children.Add(headbar); // Setup week text block - for (int week = 1; week <= WEEK_NAMES.Length; ++week) { + for (int week = 1; week <= WeekNames.Length; ++week) { var weekLabel = new TextBlock(); - weekLabel.Text = WEEK_NAMES[week - 1]; - weekLabel.Foreground = new SolidColorBrush(HEADBAR_COLOR.Foreground); + weekLabel.Text = WeekNames.Names[week - 1]; + weekLabel.Foreground = new SolidColorBrush(ColorConsistency.HeadbarColor.Foreground); weekLabel.HorizontalAlignment = HorizontalAlignment.Center; Grid.SetRow(weekLabel, 0); Grid.SetColumn(weekLabel, week - 1 + 1); @@ -90,7 +64,7 @@ namespace HFUTCourseSimulation.Dialog { var weekDateLabel = new TextBlock(); weekDateLabel.Text = "N/A"; - weekDateLabel.Foreground = new SolidColorBrush(HEADBAR_COLOR.Foreground); + weekDateLabel.Foreground = new SolidColorBrush(ColorConsistency.HeadbarColor.Foreground); weekDateLabel.HorizontalAlignment = HorizontalAlignment.Center; Grid.SetRow(weekDateLabel, 1); Grid.SetColumn(weekDateLabel, week - 1 + 1); @@ -100,7 +74,7 @@ namespace HFUTCourseSimulation.Dialog { } // Setup sidebar and index text block for (int index = 1; index <= CurrentSemester.indexCount; ++index) { - var color = GetSidebarColor(CurrentSemester.GetIndexKind(index)); + var color = ColorConsistency.GetSidebarColor(CurrentSemester.GetIndexKind(index)); var sidebar = new Rectangle(); sidebar.Fill = new SolidColorBrush(color.Background); @@ -127,12 +101,12 @@ namespace HFUTCourseSimulation.Dialog { Grid.SetRowSpan(cornerLabel, 2); uiCoreGrid.Children.Add(cornerLabel); // Add chessboard - for (int week = 1; week <= WEEK_NAMES.Length; ++week) { + for (int week = 1; week <= WeekNames.Length; ++week) { for (int index = 1; index <= CurrentSemester.indexCount; ++index) { if ((week + index) % 2 != 0) continue; var chessboard = new Rectangle(); - chessboard.Fill = new SolidColorBrush(Color.FromArgb(10, 0, 0, 0)); + chessboard.Fill = new SolidColorBrush(ColorConsistency.ChessboardColor); Grid.SetColumn(chessboard, week - 1 + 1); Grid.SetRow(chessboard, index - 1 + 2); uiCoreGrid.Children.Add((chessboard)); diff --git a/HFUTCourseSimulation/HFUTCourseSimulation.csproj b/HFUTCourseSimulation/HFUTCourseSimulation.csproj index 03cd914..917f1a0 100644 --- a/HFUTCourseSimulation/HFUTCourseSimulation.csproj +++ b/HFUTCourseSimulation/HFUTCourseSimulation.csproj @@ -89,10 +89,13 @@ + + + Designer diff --git a/HFUTCourseSimulation/Kernel/Render.cs b/HFUTCourseSimulation/Kernel/Render.cs new file mode 100644 index 0000000..d9e1ab2 --- /dev/null +++ b/HFUTCourseSimulation/Kernel/Render.cs @@ -0,0 +1,273 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Imaging; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; +using HFUTCourseSimulation.Util; + +namespace HFUTCourseSimulation.Kernel { + + /// + /// 渲染器的图片的几何形状 + /// + /// 渲染后的图片中,教学周从左到右从上到下排布。 + /// 整个图片的长和宽上的教学周数量,要使得最终的图形尽量为正方形。 + /// 每个教学周外围有MARGIN的边框留白。 + /// 教学周中,最上方是Title,书写当前的教学周号。 + /// 然后是表格,表格上有Headbar显示星期几和日期,左有Sidebar显示节次。 + /// 最后是内中表格。 + /// + internal class RenderGeometry { + /// + /// 教学周标题所占的高度 + /// + public static readonly int TITLE_HEIGHT = 50; + /// + /// 表头(星期几和日期)所占的高度 + /// + public static readonly int HEADBAR_HEIGHT = 40; + /// + /// 侧边(节次号)所占的高度 + /// + public static readonly int SIDEBAR_WIDTH = 25; + /// + /// 每个节次Cell的宽度 + /// + public static readonly int CELL_WIDTH = 150; + /// + /// 每个节次Cell的高度 + /// + public static readonly int CELL_HEIGHT = 80; + /// + /// 每个表格之间的长宽上的间距 + /// + public static readonly int MARGIN = 20; + + public RenderGeometry(int weekCount, int indexCount) { + _horizontalCount = (int)Math.Sqrt(weekCount) + 1; + _verticalCount = weekCount / _horizontalCount; + if (weekCount % _horizontalCount != 0) _verticalCount++; + + _weekWidth = SIDEBAR_WIDTH + 7 * CELL_WIDTH + 2 * MARGIN; + _weekHeight = TITLE_HEIGHT + HEADBAR_HEIGHT + indexCount * CELL_HEIGHT + 2 * MARGIN; + + _width = _weekWidth * _horizontalCount; + _height = _weekHeight * _verticalCount; + } + + int _horizontalCount; + int _verticalCount; + int _weekWidth; + int _weekHeight; + int _width; + int _height; + + /// + /// 图片宽度 + /// + public int Width { get { return _width; } } + /// + /// 图片高度 + /// + public int Height { get { return _height; } } + + /// + /// 获取教学周的左上角坐标(实际开始画图的开始,排除Margin) + /// + private Point GetWeekPos(int week) { + week -= 1; + + var row = week / _horizontalCount; + var column = week % _verticalCount; + return new Point(column * _weekWidth + MARGIN, row * _weekHeight + MARGIN); + } + + public Point GetTitleTextPos(int week) { + return GetWeekPos(week); + } + + /// + /// 获取课表主体的左上角的坐标 + /// + private Point GetTablePos(int week) { + var pos = GetWeekPos(week); + pos.Offset(0, TITLE_HEIGHT); + return pos; + } + + public Rectangle GetHeadbarBoxRect(int week) { + var pos = GetTablePos(week); + return new Rectangle(pos, new Size(7 * CELL_WIDTH, HEADBAR_HEIGHT)); + } + + public Point GetHeadbarTextPos(int week, int day) { + day -= 1; + + var pos = GetTablePos(week); + pos.Offset(day * CELL_WIDTH, 0); + return pos; + } + + public Rectangle GetSidebarBoxRect(int week, int index) { + index -= 1; + + var pos = GetTablePos(week); + return new Rectangle(pos, new Size(0, index * CELL_HEIGHT)); + } + + public Point GetSidebarTextPos(int week, int index) { + index -= 1; + + var pos = GetTablePos(week); + pos.Offset(0, index * CELL_HEIGHT); + return pos; + } + + /// + /// 获取课表课程区域的左上角坐标(排除顶栏和侧栏) + /// + private Point GetBodyPos(int week) { + var pos = GetTablePos(week); + pos.Offset(SIDEBAR_WIDTH, HEADBAR_HEIGHT); + return pos; + } + + /// + /// 获取指定周,指定星期,指定节次的表格的左上角坐标 + /// + private Point GetCellPos(int week, int day, int index) { + day -= 1; + index -= 1; + + var pos = GetBodyPos(week); + pos.Offset(day * CELL_WIDTH, index * CELL_HEIGHT); + return pos; + } + + public Point GetCellTextPos(int week, int day, int index) { + return GetCellPos(week, day, index); + } + + public Rectangle GetCellBoxRect(int week, int day, int index, int index_span = 1) { + var pos = GetCellPos(week, day, index); + return new Rectangle(pos, new Size(CELL_WIDTH, index_span * CELL_HEIGHT)); + } + + } + + /// + /// 渲染器笔刷集合。 + /// 渲染器工作时,需要频繁创建各种不同颜色的笔刷,此处为其缓存这些笔刷。 + /// + public class RenderBrushes { + public RenderBrushes() { + _brushes = new Dictionary(); + } + + public SolidBrush GetBrush(Color color) { + if (_brushes.TryGetValue(color, out SolidBrush brush)) { + return brush; + } else { + var new_brush = new SolidBrush(color); + _brushes.Add(color, new_brush); + return new_brush; + } + } + + public SolidBrush GetBrush(System.Windows.Media.Color color) { + return GetBrush(ColorTrans.ToGdiColor(color)); + } + + Dictionary _brushes; + } + + /// + /// 核心图片渲染器 + /// + public static class Render { + + /// + /// 渲染给定课表到给定图片中。 + /// + /// 要渲染的课表 + /// 要渲染到的图片路径 + /// 如果无误,返回true,否则返回false。 + public static bool Rending(Kernel.Data.Presentation.Semester semester, string filepath) { + try { + UnderlyingRending(semester, filepath); + return true; + } catch (Exception) { + return false; + } + } + + private static void UnderlyingRending(Kernel.Data.Presentation.Semester semester, string filepath) { + // 确定画布基本属性 + var geometry = new RenderGeometry(semester.weekCount, semester.indexCount); + + // 创建图像 + using (var img = new Bitmap(geometry.Width, geometry.Height)) { + // 创建画布 + Graphics g = Graphics.FromImage(img); + + // 创建绘画资源 + var brushes = new RenderBrushes(); + var font = new Font("Source Hans Sans CN", 12); + + // 填充背景为白色 + g.Clear(Color.White); + + // 按教学周周分别输出 + for (int week = 1; week <= semester.weekCount; ++week) { + var week_instance = semester.weeks[week - 1]; + + // 教学周文本 + g.DrawString($"教学周:{week}", font, brushes.GetBrush(Color.Black), geometry.GetTitleTextPos(week)); + + // 绘制Headbar底层 + g.FillRectangle(brushes.GetBrush(ColorConsistency.HeadbarColor.Background), geometry.GetHeadbarBoxRect(week)); + // 绘制Header文本 + for (int day = 1; day <= WeekNames.Length; ++day) { + var day_instance = week_instance.days[day - 1]; + g.DrawString($@"{WeekNames.Names[day - 1]} +{day_instance.date}", font, brushes.GetBrush(ColorConsistency.HeadbarColor.Foreground), geometry.GetHeadbarTextPos(week, day)); + } + + // 绘制Sidebar底层和文本 + for (int index = 1; index <= semester.indexCount; ++index) { + var colorPair = ColorConsistency.GetSidebarColor(semester.GetIndexKind(index)); + g.FillRectangle(brushes.GetBrush(colorPair.Background), geometry.GetSidebarBoxRect(week, index)); + g.DrawString(index.ToString(), font, brushes.GetBrush(colorPair.Foreground), geometry.GetSidebarTextPos(week, index)); + } + + // 绘制Chessboard + for (int day = 1; day <= WeekNames.Length; ++day) { + for (int index = 1; index <= semester.indexCount; ++index) { + if ((day + index) % 2 == 0) continue; + g.FillRectangle(brushes.GetBrush(ColorConsistency.ChessboardColor), geometry.GetCellBoxRect(week, day, index)); + } + } + + // 绘制课程 + for (int day = 1; day <= WeekNames.Length; ++day) { + var day_instance = week_instance.days[day - 1]; + foreach (var lesson_instance in day_instance.lessons) { + g.FillRectangle(brushes.GetBrush(lesson_instance.color.Background), geometry.GetCellBoxRect(week, day, lesson_instance.startIndex, lesson_instance.indexSpan)); + g.DrawString($@"{lesson_instance.name} +{lesson_instance.description}", font, brushes.GetBrush(lesson_instance.color.Foreground), geometry.GetCellTextPos(week, day, lesson_instance.startIndex)); + } + } + } + + // 保存到文件 + using (var fs = new FileStream(filepath, FileMode.OpenOrCreate, FileAccess.Write, FileShare.None)) { + img.Save(fs, ImageFormat.Png); + } + } + } + } +} diff --git a/HFUTCourseSimulation/MainWindow.xaml.cs b/HFUTCourseSimulation/MainWindow.xaml.cs index 832c3da..bff5a37 100644 --- a/HFUTCourseSimulation/MainWindow.xaml.cs +++ b/HFUTCourseSimulation/MainWindow.xaml.cs @@ -278,6 +278,21 @@ namespace HFUTCourseSimulation { //var res = ImageExport.Export(originDate, weekCount, getFile); //if (!res) MessageBox.Show("当前课程安排存在冲突,请通过实时预览消除所有错误后才能使用此功能来导出为图片", "错误", MessageBoxButton.OK, MessageBoxImage.Error, MessageBoxResult.OK); //else MessageBox.Show("导出成功", "关于", MessageBoxButton.OK, MessageBoxImage.Information, MessageBoxResult.OK); + + var semester = CurrentSemester.ToStorage(); + var reporter = new Kernel.Reporter(); + var rv = Kernel.Arranger.Arrange(semester, reporter); + if (rv is null) return; + + // Fetch file path. + var filepath = Util.Win32Dialog.SaveRender(); + if (filepath is null) return; + + if (Kernel.Render.Rending(rv, filepath)) { + Win32Dialog.Info("导出成功", "导出结果"); + } else { + Win32Dialog.Error("导出失败。请检查文件是否被占用,或检查GDI是否能正常使用。", "导出结果"); + } } private void uiMenuAbout_Click(object sender, RoutedEventArgs e) { diff --git a/HFUTCourseSimulation/Util/ColorConsistency.cs b/HFUTCourseSimulation/Util/ColorConsistency.cs new file mode 100644 index 0000000..9d0cddb --- /dev/null +++ b/HFUTCourseSimulation/Util/ColorConsistency.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Media; + +namespace HFUTCourseSimulation.Util { + + /// + /// 用于在Simulator和Render之间确保采用的色彩的一致性 + /// + public static class ColorConsistency { + + /// + /// 获取标题栏的配色(星期几和日期) + /// + public static ColorPair HeadbarColor => ColorPreset.MdColors.Grey; + + /// + /// 获取侧边栏的配色(节次号) + /// + /// + /// + public static ColorPair GetSidebarColor(Kernel.Data.Presentation.IndexKind kind) { + switch (kind) { + case Kernel.Data.Presentation.IndexKind.Dawn: + return ColorPreset.MdColors.Cyan; + case Kernel.Data.Presentation.IndexKind.Morning: + return ColorPreset.MdColors.LightBlue; + case Kernel.Data.Presentation.IndexKind.Afternoon: + return ColorPreset.MdColors.Amber; + case Kernel.Data.Presentation.IndexKind.Night: + return ColorPreset.MdColors.BlueGrey; + default: + return ColorPreset.MdColors.Grey; + } + } + + /// + /// 棋盘中的异色颜色的配置 + /// + public static Color ChessboardColor => Color.FromArgb(10, 0, 0, 0); + + } + +} diff --git a/HFUTCourseSimulation/Util/ColorTrans.cs b/HFUTCourseSimulation/Util/ColorTrans.cs index 9178e8b..6e858c0 100644 --- a/HFUTCourseSimulation/Util/ColorTrans.cs +++ b/HFUTCourseSimulation/Util/ColorTrans.cs @@ -5,7 +5,7 @@ using System.Text; using System.Threading.Tasks; using WpfColor = System.Windows.Media.Color; -using WinformColor = System.Drawing.Color; +using GdiColor = System.Drawing.Color; using StandardColorPair = HFUTCourseSimulation.Util.ColorPair; using StorageColorPair = HFUTCourseSimulation.Kernel.Data.Storage.ColorPair; @@ -16,12 +16,12 @@ namespace HFUTCourseSimulation.Util { #region Color Transform - public static WinformColor ToWinformColor(int argb) { - return WinformColor.FromArgb(argb); + public static GdiColor ToGdiColor(int argb) { + return GdiColor.FromArgb(argb); } - public static WinformColor ToWinformColor(WpfColor c) { - return WinformColor.FromArgb(c.A, c.R, c.G, c.B); + public static GdiColor ToGdiColor(WpfColor c) { + return GdiColor.FromArgb(c.A, c.R, c.G, c.B); } public static WpfColor ToWpfColor(int _argb) { @@ -33,11 +33,11 @@ namespace HFUTCourseSimulation.Util { return WpfColor.FromArgb((byte)a, (byte)r, (byte)g, (byte)b); } - public static WpfColor ToWpfColor(WinformColor c) { + public static WpfColor ToWpfColor(GdiColor c) { return WpfColor.FromArgb(c.A, c.R, c.G, c.B); } - public static int ToInt(WinformColor c) { + public static int ToInt(GdiColor c) { return c.ToArgb(); } diff --git a/HFUTCourseSimulation/Util/WeekNames.cs b/HFUTCourseSimulation/Util/WeekNames.cs new file mode 100644 index 0000000..7839531 --- /dev/null +++ b/HFUTCourseSimulation/Util/WeekNames.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace HFUTCourseSimulation.Util { + public static class WeekNames { + + public static readonly string[] Names = new string[] { + "星期一", + "星期二", + "星期三", + "星期四", + "星期五", + "星期六", + "星期日" + }; + + public static int Length => Names.Length; + + } +}