Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .squad/agents/cheritto/history.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,3 +154,15 @@
- **Files:** PresentationService.Deduplication.cs (new partial), PptxTools.Deduplication.cs, DeduplicateMediaResult.cs, DeduplicateMediaTests.cs
- **Build:** 0 errors; 542/542 tests passing (10 new tests)
- **PR:** on branch squad/84-deduplicate-media

### Issue #85 — pptx_optimize_images (2026-03-26)
- **Implementation:** Phase 4 Tier 2 write operation — compresses/optimizes images by downscaling, format conversion, and recompression
- **Dependency:** Magick.NET-Q8-AnyCPU v14.2.0 (cross-platform ImageMagick wrapper, Apache 2.0 license per Nate's research)
- **Key logic:** Read image dimensions with MagickImageInfo → find Picture shape via Blip.Embed → extract display dimensions from Transform2D.Extents → calculate target dimensions based on targetDpi (EMU → pixels: emu / 914400 * dpi) → downscale if pixel dimensions exceed display dimensions → convert BMP/TIFF to PNG/JPEG → recompress JPEG at specified quality → only replace if optimized size < original
- **Type challenges:** MagickImageInfo returns uint for Width/Height; MagickImage.Resize() requires uint; MagickImage.Width/Height properties are uint; model uses int — required explicit casts throughout
- **Namespace aliasing:** Used `P = DocumentFormat.OpenXml.Presentation` and `A = DocumentFormat.OpenXml.Drawing` to resolve ambiguous Picture/BlipFill references
- **Models:** ImageOptimizationResult with OptimizedImageInfo; reuses ValidationStatus from RemoveLayoutsResult.cs
- **Files:** PresentationService.ImageOptimization.cs (new partial), PptxTools.Optimization.cs (added tool method), ImageOptimizationResult.cs
- **Build:** 0 errors; 542/542 tests passing (no new tests yet — Shiherlis owns test creation)
- **PR:** #93 on branch squad/85-optimize-images

30 changes: 30 additions & 0 deletions .squad/agents/nate/history.md
Original file line number Diff line number Diff line change
Expand Up @@ -250,3 +250,33 @@
**Deliverable:** `.squad/decisions/inbox/nate-phase4-openxml-research.md` — 40 KB comprehensive research with code sketches, gotchas, validation strategies for all 7 issues.

**Impact:** Unblocks Phase 4 implementation planning. Team can now scope work with confidence: #80–#82 are quick wins; #83–#84 require diligent testing; #85 is optional enhancement; #86 safely deferred.

### 2026-03-26: Magick.NET Feasibility Research — Issue #85 (Image Compression)

**Research Scope:** Jon directive to investigate Magick.NET (instead of SkiaSharp) for issue #85 image compression/optimization tool. Evaluate feasibility, cross-platform support, bundle size, API surface, and integration with PresentationService.

**Key Findings:**
- **Verdict: GO** ✅ — Magick.NET is fully viable for #85. Covers all requirements (resize, re-encode JPEG quality 85%, convert BMP/TIFF→PNG/JPEG, read dimensions, stream-based I/O)
- **Licensing:** Apache-2.0 (permissive, commercial-friendly, OSI-approved)
- **Cross-Platform:** Full Linux support on ubuntu-latest; bundles static-linked native binaries (no separate ImageMagick install needed)
- **Recommended Package:** `Magick.NET-Q8-x64` v14.11.0+ (Q8=8-bit component, x64=platform-specific, reduces bundle size)
- **Bundle Size Impact:** ~15-18 MB added to published binary (AnyCPU variant would be ~27-35 MB). Acceptable for open-source MCP server.
- **API Strength:** Clean Magick.NET surface for all operations:
- Dimensions: `MagickImageInfo.Width`, `MagickImageInfo.Height`
- Resize: `image.Resize(width, height)` with aspect ratio control
- JPEG Quality: `image.Quality = 85` before `image.Write()`
- Format Conversion: `image.Format = MagickFormat.Jpeg` / `MagickFormat.Png`
- I/O: Stream-based (`new MagickImage(stream)`, `image.Write(outputStream)`)
- **Integration Pattern:** Create `PresentationService.ImageOptimization.cs` with public `OptimizeImages()` tool method; thin wrapper around Magick.NET with in-place image replacement via `imagePart.FeedData(stream)`
- **Magick.NET vs. SkiaSharp:** Magick.NET slower for simple resize (SkiaSharp 2-4x faster) but superior for image compression workflow: richer format support (100+ formats), JPEG quality control, BMP/TIFF handling. Bundle size tradeoff acceptable; performance not a bottleneck for one-time optimization.
- **Risks:** Native library compatibility on Linux (mitigated by .csproj properties for binary bundling), aspect ratio preservation (use `Resize(width, 0)` for auto-height), JPEG quality trade-offs (document as expected for compression)
- **Implementation Estimate:** 6-8 hours (dependency setup, tool method, E2E test, README update)

**Deliverable:** `.squad/decisions/inbox/nate-magick-net-research.md` — 10 KB feasibility report with API sketches, integration pattern, bundle size breakdown, comparison table vs. SkiaSharp, gotchas, and handoff recommendations for Cheritto.

**Impact:** Unblocks #85 decision. Go/no-go clear; Jon's Magick.NET preference validated. Ready for development phase with high confidence in approach.

**File Paths:**
- `src/PptxMcp/Services/PresentationService.Media.cs` (lines 104–145) — current image traversal pattern (Foundation for new ImageOptimization.cs)
- NuGet: https://www.nuget.org/packages/Magick.NET-Q8-x64/
- Magick.NET GitHub: https://github.com/dlemstra/Magick.NET (reference for API/cross-platform docs)
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ See [docs/QUICKSTART.md](docs/QUICKSTART.md) for a full walkthrough.
| `pptx_find_unused_layouts` | Find unused slide masters and layouts with estimated space savings |
| `pptx_remove_unused_layouts` | Remove unused slide layouts and orphaned masters with before/after validation |
| `pptx_deduplicate_media` | Deduplicate identical media by hash, redirect references, remove orphaned copies |
| `pptx_optimize_images` | Compress/optimize images by downscaling, format conversion, and recompression |

**When to use `pptx_update_slide_data` vs `pptx_update_text`:** Use `pptx_update_slide_data` when shapes have descriptive names (check `pptx_get_slide_content`) — it targets shapes by name and preserves their existing formatting. Use `pptx_update_text` for anonymous placeholders identified only by index.

Expand Down
49 changes: 49 additions & 0 deletions src/PptxMcp/Models/ImageOptimizationResult.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
namespace PptxMcp.Models;

/// <summary>Details about a single optimized image.</summary>
/// <param name="ImagePath">Package URI of the image part.</param>
/// <param name="OriginalFormat">Original image format (PNG, JPEG, BMP, TIFF, etc.).</param>
/// <param name="OptimizedFormat">Format after optimization.</param>
/// <param name="OriginalWidth">Original image width in pixels.</param>
/// <param name="OriginalHeight">Original image height in pixels.</param>
/// <param name="OptimizedWidth">Width after downscaling (same as original if not downscaled).</param>
/// <param name="OptimizedHeight">Height after downscaling (same as original if not downscaled).</param>
/// <param name="OriginalSizeBytes">Original image size in bytes.</param>
/// <param name="OptimizedSizeBytes">Size after optimization in bytes.</param>
/// <param name="BytesSaved">Bytes saved by optimization.</param>
/// <param name="Action">Description of action taken (downscaled, converted, recompressed, skipped).</param>
public record OptimizedImageInfo(
string ImagePath,
string OriginalFormat,
string OptimizedFormat,
int OriginalWidth,
int OriginalHeight,
int OptimizedWidth,
int OptimizedHeight,
long OriginalSizeBytes,
long OptimizedSizeBytes,
long BytesSaved,
string Action);

/// <summary>Structured result for pptx_optimize_images.</summary>
/// <param name="Success">True when the operation completed without errors.</param>
/// <param name="FilePath">Path to the modified presentation file.</param>
/// <param name="ImagesProcessed">Number of images that were optimized.</param>
/// <param name="ImagesSkipped">Number of images that were skipped (no optimization possible).</param>
/// <param name="TotalBytesBefore">Total size of all images before optimization.</param>
/// <param name="TotalBytesAfter">Total size of all images after optimization.</param>
/// <param name="TotalBytesSaved">Total bytes saved by optimization.</param>
/// <param name="OptimizedImages">Details for each optimized or skipped image.</param>
/// <param name="Validation">OpenXML validation status before and after.</param>
/// <param name="Message">Human-readable status or error message.</param>
public record ImageOptimizationResult(
bool Success,
string FilePath,
int ImagesProcessed,
int ImagesSkipped,
long TotalBytesBefore,
long TotalBytesAfter,
long TotalBytesSaved,
IReadOnlyList<OptimizedImageInfo> OptimizedImages,
ValidationStatus Validation,
string Message);
1 change: 1 addition & 0 deletions src/PptxMcp/PptxMcp.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@

<ItemGroup>
<PackageReference Include="DocumentFormat.OpenXml" Version="3.5.1" />
<PackageReference Include="Magick.NET-Q8-AnyCPU" Version="14.2.0" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="10.0.5" />
<PackageReference Include="ModelContextProtocol" Version="1.1.0" />
</ItemGroup>
Expand Down
Loading
Loading