From 820dd8a0f5d7cafa92c937a23277064084072f57 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 24 Apr 2026 08:37:42 +0000 Subject: [PATCH 1/2] Initial plan From 21b3c99c85a54b3f5e6d3a8105208a60c06c5690 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 24 Apr 2026 08:47:35 +0000 Subject: [PATCH 2/2] feat: add image padding support Agent-Logs-Url: https://github.com/slaveOftime/ACadSharp.Image/sessions/5fa478be-5610-402f-8b14-6df8986b3770 Co-authored-by: albertwoo <8017681+albertwoo@users.noreply.github.com> --- ACadSharp.Image.Cli/CliOptions.cs | 4 + ACadSharp.Image.Cli/Program.cs | 38 ++++++++- ACadSharp.Image.Tests/ImageExporterTests.cs | 52 ++++++++++++ ACadSharp.Image/ImageConfiguration.cs | 85 +++++++++++++++++++ .../Rendering/ImageRenderContext.cs | 15 +++- README.md | 13 ++- 6 files changed, 200 insertions(+), 7 deletions(-) diff --git a/ACadSharp.Image.Cli/CliOptions.cs b/ACadSharp.Image.Cli/CliOptions.cs index ac5ef87..f5692c0 100644 --- a/ACadSharp.Image.Cli/CliOptions.cs +++ b/ACadSharp.Image.Cli/CliOptions.cs @@ -6,6 +6,10 @@ internal sealed record CliOptions( string? Format, int Width, int Height, + int PaddingLeft, + int PaddingTop, + int PaddingRight, + int PaddingBottom, string BackgroundColor, int Quality, bool ExportPaperLayouts, diff --git a/ACadSharp.Image.Cli/Program.cs b/ACadSharp.Image.Cli/Program.cs index a006c1a..2f671dd 100644 --- a/ACadSharp.Image.Cli/Program.cs +++ b/ACadSharp.Image.Cli/Program.cs @@ -72,6 +72,7 @@ private static void Configure(ImageConfiguration configuration, CliOptions optio { configuration.Width = options.Width; configuration.Height = options.Height; + configuration.SetPadding(options.PaddingLeft, options.PaddingTop, options.PaddingRight, options.PaddingBottom); configuration.OutputQuality = options.Quality; configuration.BackgroundColor = ParseColor(options.BackgroundColor); @@ -141,6 +142,10 @@ private static CliOptions ParseArgs(IReadOnlyList args) string backgroundColor = "white"; int width = ImageConfiguration.DefaultWidth; int height = ImageConfiguration.DefaultHeight; + int paddingLeft = 0; + int paddingTop = 0; + int paddingRight = 0; + int paddingBottom = 0; int quality = 90; bool exportPaperLayouts = false; List hideLayers = new(); @@ -168,6 +173,10 @@ private static CliOptions ParseArgs(IReadOnlyList args) case "-H": height = ParsePositiveInt(GetRequiredValue(args, ref i, current), current); break; + case "--padding": + case "-p": + (paddingLeft, paddingTop, paddingRight, paddingBottom) = ParsePadding(GetRequiredValue(args, ref i, current), current); + break; case "--background": case "-b": backgroundColor = GetRequiredValue(args, ref i, current); @@ -196,7 +205,7 @@ private static CliOptions ParseArgs(IReadOnlyList args) throw new InvalidOperationException("An input .dxf or .dwg file is required."); } - return new CliOptions(inputPath, outputPath, format, width, height, backgroundColor, quality, exportPaperLayouts, hideLayers); + return new CliOptions(inputPath, outputPath, format, width, height, paddingLeft, paddingTop, paddingRight, paddingBottom, backgroundColor, quality, exportPaperLayouts, hideLayers); } private static int ParsePositiveInt(string value, string argumentName) @@ -219,6 +228,32 @@ private static int ParseQuality(string value, string argumentName) throw new InvalidOperationException($"Argument {argumentName} must be between 1 and 100."); } + private static (int Left, int Top, int Right, int Bottom) ParsePadding(string value, string argumentName) + { + string[] parts = value.Split(',', StringSplitOptions.TrimEntries); + int[] parsed = parts + .Select(part => ParseNonNegativeInt(part, argumentName)) + .ToArray(); + + return parsed.Length switch + { + 1 => (parsed[0], parsed[0], parsed[0], parsed[0]), + 2 => (parsed[0], parsed[1], parsed[0], parsed[1]), + 4 => (parsed[0], parsed[1], parsed[2], parsed[3]), + _ => throw new InvalidOperationException($"Argument {argumentName} must contain 1, 2, or 4 comma-separated integers."), + }; + } + + private static int ParseNonNegativeInt(string value, string argumentName) + { + if (int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out int parsed) && parsed >= 0) + { + return parsed; + } + + throw new InvalidOperationException($"Argument {argumentName} values must be zero or greater."); + } + private static string GetRequiredValue(IReadOnlyList args, ref int index, string argumentName) { if (index + 1 >= args.Count || args[index + 1].StartsWith('-')) @@ -261,6 +296,7 @@ private static void WriteHelp() -f, --format png, bmp, jpg, jpeg, gif, webp. -w, --width Output width in pixels. Default: 1600. -H, --height Output height in pixels. Default: 900. + -p, --padding Padding in pixels: , , or . -b, --background Background color name or hex value. Default: white. -q, --quality <1-100> Output quality for lossy formats. Default: 90. --paper-layouts Export paper layouts instead of model space. diff --git a/ACadSharp.Image.Tests/ImageExporterTests.cs b/ACadSharp.Image.Tests/ImageExporterTests.cs index ec74e44..01b7d72 100644 --- a/ACadSharp.Image.Tests/ImageExporterTests.cs +++ b/ACadSharp.Image.Tests/ImageExporterTests.cs @@ -1,7 +1,11 @@ using ACadSharp.Entities; using ACadSharp.IO; +using ACadSharp.Image.Rendering; +using ACadSharp.Objects; using ACadSharp.Tables; using CSMath; +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.PixelFormats; namespace ACadSharp.Image.Tests; @@ -22,6 +26,10 @@ public void ConfigurationUsesDefaultCanvasSize() Assert.Equal(ImageConfiguration.DefaultWidth, configuration.Width); Assert.Equal(ImageConfiguration.DefaultHeight, configuration.Height); + Assert.Equal(0, configuration.PaddingLeft); + Assert.Equal(0, configuration.PaddingTop); + Assert.Equal(0, configuration.PaddingRight); + Assert.Equal(0, configuration.PaddingBottom); } [Fact] @@ -41,6 +49,50 @@ public void RenderUsesConfiguredCanvasSize() Assert.Equal(600, page.Canvas.Height); } + [Fact] + public void PageContextUsesConfiguredPadding() + { + ImageConfiguration configuration = new() + { + Width = 100, + Height = 80, + }; + configuration.SetPadding(10, 20, 30, 20); + + ImagePage page = new() + { + Layout = new Layout("padding-page") + { + PaperWidth = 12, + PaperHeight = 8, + }, + }; + + using Image canvas = new(configuration.Width, configuration.Height); + ImageRenderContext context = ImageRenderContext.CreatePageContext(canvas, page, configuration); + + Assert.Equal(5f, context.PixelsPerUnit); + Assert.Equal(10f, context.OffsetX); + Assert.Equal(20f, context.OffsetY); + } + + [Fact] + public void RenderThrowsWhenPaddingConsumesCanvas() + { + BlockRecord block = new("padding-overflow-block"); + block.Entities.Add(new Line(new XYZ(0, 0, 0), new XYZ(10, 10, 0))); + + ImageExporter exporter = new(); + exporter.Configuration.Width = 20; + exporter.Configuration.Height = 20; + exporter.Configuration.SetPadding(10, 0, 10, 0); + exporter.Add(block); + + InvalidOperationException ex = Assert.Throws(() => exporter.Render()); + + Assert.Contains("Padding", ex.Message, StringComparison.Ordinal); + } + [Fact] public void RenderSplineBlockDoesNotReportNotImplemented() { diff --git a/ACadSharp.Image/ImageConfiguration.cs b/ACadSharp.Image/ImageConfiguration.cs index 036ceca..266361b 100644 --- a/ACadSharp.Image/ImageConfiguration.cs +++ b/ACadSharp.Image/ImageConfiguration.cs @@ -93,6 +93,14 @@ public sealed class ImageConfiguration private int _outputQuality = 90; + private int _paddingTop; + + private int _paddingRight; + + private int _paddingBottom; + + private int _paddingLeft; + /// /// Gets or sets the number of segments used to approximate arcs and circles during polygonal tessellation. /// @@ -128,6 +136,42 @@ public sealed class ImageConfiguration /// public float LineWeightScale { get; set; } = 1f; + /// + /// Gets or sets the top padding in pixels applied inside the output canvas. + /// + public int PaddingTop + { + get => this._paddingTop; + set => this._paddingTop = validateNonNegative(value, nameof(this.PaddingTop)); + } + + /// + /// Gets or sets the right padding in pixels applied inside the output canvas. + /// + public int PaddingRight + { + get => this._paddingRight; + set => this._paddingRight = validateNonNegative(value, nameof(this.PaddingRight)); + } + + /// + /// Gets or sets the bottom padding in pixels applied inside the output canvas. + /// + public int PaddingBottom + { + get => this._paddingBottom; + set => this._paddingBottom = validateNonNegative(value, nameof(this.PaddingBottom)); + } + + /// + /// Gets or sets the left padding in pixels applied inside the output canvas. + /// + public int PaddingLeft + { + get => this._paddingLeft; + set => this._paddingLeft = validateNonNegative(value, nameof(this.PaddingLeft)); + } + /// /// Gets or sets the background color of the rendered image. /// @@ -250,8 +294,49 @@ public float GetLineWeightPixels(LineWeightType lineWeight) return Math.Max(1f, pixels * this.LineWeightScale); } + /// + /// Applies the same padding to all four sides of the output canvas. + /// + /// The padding in pixels for each side. + public void SetPadding(int padding) + { + this.SetPadding(padding, padding, padding, padding); + } + + /// + /// Applies horizontal and vertical padding to the output canvas. + /// + /// The padding in pixels for the left and right sides. + /// The padding in pixels for the top and bottom sides. + public void SetPadding(int horizontal, int vertical) + { + this.SetPadding(horizontal, vertical, horizontal, vertical); + } + + /// + /// Applies padding to each side of the output canvas. + /// + /// The left padding in pixels. + /// The top padding in pixels. + /// The right padding in pixels. + /// The bottom padding in pixels. + public void SetPadding(int left, int top, int right, int bottom) + { + this.PaddingLeft = left; + this.PaddingTop = top; + this.PaddingRight = right; + this.PaddingBottom = bottom; + } + internal void Notify(string message, NotificationType notificationType, Exception? ex = null) { this.OnNotification?.Invoke(this, new NotificationEventArgs(message, notificationType, ex)); } + + private static int validateNonNegative(int value, string propertyName) + { + return value >= 0 + ? value + : throw new ArgumentOutOfRangeException(propertyName, "Padding must be zero or greater."); + } } diff --git a/ACadSharp.Image/Rendering/ImageRenderContext.cs b/ACadSharp.Image/Rendering/ImageRenderContext.cs index 96fcdda..9109ddb 100644 --- a/ACadSharp.Image/Rendering/ImageRenderContext.cs +++ b/ACadSharp.Image/Rendering/ImageRenderContext.cs @@ -57,17 +57,24 @@ public static ImageRenderContext CreatePageContext( ImagePage page, ImageConfiguration configuration) { + int drawableWidth = configuration.Width - configuration.PaddingLeft - configuration.PaddingRight; + int drawableHeight = configuration.Height - configuration.PaddingTop - configuration.PaddingBottom; + if (drawableWidth <= 0 || drawableHeight <= 0) + { + throw new InvalidOperationException("Padding must leave at least one drawable pixel in both dimensions."); + } + Layout layout = page.Layout ?? new Layout("default_page"); double pageWidth = Math.Max(1d, layout.PaperWidth); double pageHeight = Math.Max(1d, layout.PaperHeight); float pixelsPerUnit = Math.Min( - configuration.Width / (float)pageWidth, - configuration.Height / (float)pageHeight); + drawableWidth / (float)pageWidth, + drawableHeight / (float)pageHeight); float scaledWidth = (float)pageWidth * pixelsPerUnit; float scaledHeight = (float)pageHeight * pixelsPerUnit; - float offsetX = (configuration.Width - scaledWidth) / 2f; - float offsetY = (configuration.Height - scaledHeight) / 2f; + float offsetX = configuration.PaddingLeft + ((drawableWidth - scaledWidth) / 2f); + float offsetY = configuration.PaddingBottom + ((drawableHeight - scaledHeight) / 2f); double originX = -page.Translation.X - layout.UnprintableMargin.Left; double originY = -page.Translation.Y - layout.UnprintableMargin.Bottom; diff --git a/README.md b/README.md index d8c9778..f305156 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ Transform CAD drawings into raster images for **previews**, **CI/CD pipelines**, - 🎨 **Multi-format export** — PNG, BMP, JPEG, GIF, and WebP support - 📐 **Full CAD support** — Render DXF and DWG files with ACadSharp -- 🖼️ **Customizable output** — Control width, height, background color, and quality +- 🖼️ **Customizable output** — Control width, height, padding, background color, and quality - 📊 **Space support** — Model space, paper layouts, and viewports - 🎭 **Layer filtering** — Hide specific layers with `--hide-layer` option - ⚡ **CLI tool** — Cross-platform command-line interface for automation @@ -67,6 +67,7 @@ var document = DwgReader.Read("part.dwg"); var exporter = new ImageExporter("output.webp"); exporter.Configuration.Width = 2000; exporter.Configuration.Height = 1400; +exporter.Configuration.SetPadding(24, 12); exporter.Configuration.BackgroundColor = Color.Parse("#ffffff"); exporter.Configuration.OutputQuality = 90; @@ -100,6 +101,14 @@ cad-to-image "drawing.dxf" --format webp --width 1400 --height 1400 --quality 85 cad-to-image "part.dwg" --format png --width 1800 --height 1200 --background "#0c0c0c" ``` +**Add padding around the drawing:** + +```bash +cad-to-image "part.dwg" --format png --padding 24 +cad-to-image "part.dwg" --format png --padding 24,12 +cad-to-image "part.dwg" --format png --padding 24,12,40,20 +``` + **Hide multiple layers:** ```bash @@ -125,6 +134,7 @@ Options: -f, --format png, bmp, jpg, jpeg, gif, webp. -w, --width Output width in pixels. Default: 1600. -H, --height Output height in pixels. Default: 900. + -p, --padding Padding in pixels: , , or . -b, --background Background color name or hex value. Default: white. -q, --quality <1-100> Output quality for lossy formats. Default: 90. --paper-layouts Export paper layouts instead of model space. @@ -272,4 +282,3 @@ This project is released under the [MIT License](LICENSE). If you find this project helpful, please consider giving it a ⭐️ on GitHub! It helps others discover the project. **Questions or issues?** [Open an issue](https://github.com/slaveoftime/ACadSharp.Image/issues) or start a [Discussion](https://github.com/slaveoftime/ACadSharp.Image/discussions). -