116 lines
4.1 KiB
C#
116 lines
4.1 KiB
C#
using System;
|
||
using System.IO;
|
||
using System.Threading.Tasks;
|
||
using System.Windows;
|
||
using System.Windows.Media;
|
||
using System.Windows.Media.Imaging;
|
||
|
||
namespace BackgroundBuilder.Services
|
||
{
|
||
public class ImageService : IImageService
|
||
{
|
||
|
||
// no longer injected — use a private constant
|
||
private static readonly Thickness OverlayOffset = new(10, 58, 10, 58);
|
||
|
||
public async Task<BitmapImage> LoadAsync(string path)
|
||
{
|
||
// Load image from disk into BitmapImage
|
||
var bmp = new BitmapImage();
|
||
using var stream = File.OpenRead(path);
|
||
bmp.BeginInit();
|
||
bmp.UriSource = new Uri(path, UriKind.Absolute);
|
||
bmp.CacheOption = BitmapCacheOption.OnLoad;
|
||
bmp.EndInit();
|
||
// no real async work—return completed task
|
||
return await Task.FromResult(bmp);
|
||
}
|
||
|
||
public async Task<(string primaryPath, string? overlayPath)> SaveAsync(
|
||
FrameworkElement overlay,
|
||
BitmapImage background,
|
||
string primaryPath,
|
||
string? overlayPath = null)
|
||
{
|
||
var compositeBmp = RenderComposite(overlay, background, OverlayOffset);
|
||
SaveBitmap(compositeBmp, primaryPath);
|
||
|
||
string? savedOverlayPath = null;
|
||
if (!string.IsNullOrWhiteSpace(overlayPath))
|
||
{
|
||
var overlayBmp = RenderComposite(overlay, null, new Thickness(0));
|
||
SaveBitmap(overlayBmp, overlayPath!);
|
||
savedOverlayPath = overlayPath;
|
||
}
|
||
|
||
return await Task.FromResult((primaryPath, savedOverlayPath));
|
||
}
|
||
|
||
/// <summary>
|
||
/// Renders a <paramref name="background"/> plus the <paramref name="mainGrid"/>
|
||
/// at bottom‐right offset by <paramref name="offset"/>. If background is null,
|
||
/// only the mainGrid is rendered at (0,0).
|
||
/// </summary>
|
||
private static RenderTargetBitmap RenderComposite(
|
||
FrameworkElement mainGrid,
|
||
BitmapImage? background,
|
||
Thickness offset)
|
||
{
|
||
// Determine canvas size
|
||
int width = background?.PixelWidth ?? (int)mainGrid.ActualWidth;
|
||
int height = background?.PixelHeight ?? (int)mainGrid.ActualHeight;
|
||
|
||
var dv = new DrawingVisual();
|
||
using (var ctx = dv.RenderOpen())
|
||
{
|
||
// Draw background if provided
|
||
if (background != null)
|
||
{
|
||
ctx.DrawImage(
|
||
background,
|
||
new Rect(0, 0, width, height));
|
||
}
|
||
|
||
// Measure & arrange the mainGrid so ActualWidth/Height are valid
|
||
mainGrid.Measure(new Size(width, height));
|
||
mainGrid.Arrange(new Rect(0, 0, mainGrid.DesiredSize.Width, mainGrid.DesiredSize.Height));
|
||
|
||
double ow = mainGrid.ActualWidth > 0 ? mainGrid.ActualWidth : mainGrid.DesiredSize.Width;
|
||
double oh = mainGrid.ActualHeight > 0 ? mainGrid.ActualHeight : mainGrid.DesiredSize.Height;
|
||
|
||
double x = width - ow - offset.Left;
|
||
double y = height - oh - offset.Right;
|
||
|
||
// Draw mainGrid at either origin or bottom-right with offset
|
||
var brush = new VisualBrush(mainGrid);
|
||
|
||
ctx.DrawRectangle(brush, null, new Rect(x, y, ow, oh));
|
||
}
|
||
|
||
var rtb = new RenderTargetBitmap(
|
||
width,
|
||
height,
|
||
96,
|
||
96,
|
||
PixelFormats.Pbgra32);
|
||
|
||
rtb.Render(dv);
|
||
return rtb;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Encodes the given bitmap as PNG and writes it to disk.
|
||
/// </summary>
|
||
private static void SaveBitmap(RenderTargetBitmap bitmap, string path)
|
||
{
|
||
// Ensure directory exists
|
||
Directory.CreateDirectory(Path.GetDirectoryName(path)!);
|
||
|
||
using var fs = new FileStream(path, FileMode.Create, FileAccess.Write);
|
||
var encoder = new PngBitmapEncoder();
|
||
encoder.Frames.Add(BitmapFrame.Create(bitmap));
|
||
encoder.Save(fs);
|
||
}
|
||
}
|
||
}
|