/
docNemo
/
clothes-graph
Обзор
Документация
Войти
/
docNemo
/
clothes-graph
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
main
src/ClothesGraph.App/Controls/UnderlayLayer.cs
141 строка
5 KB
docNemo
Запрос на слияние 'interface-to-spec' (
#1
) из interface-to-spec в main
09 авг 2026, 20:28
Верифицирован
09 авг 2026, 20:28
1bbc436
Код
Авторство
О чём код?
using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Numerics; using Avalonia; using Avalonia.Controls; using Avalonia.Media; using Avalonia.Media.Imaging; using ClothesGraph.Geometry; using ClothesGraph.Model.Underlay; using ClothesGraph.Rendering; namespace ClothesGraph.App.Controls; /// <summary> /// Подложенные изображения под чертежом. /// </summary> /// <remarks> /// Слой лежит ниже поверхности вывода, а не рисуется в ней. Поверхность /// участвует в композиции и очищается прозрачным, поэтому изображение видно /// сквозь неё — и заводить текстуры в Direct3D ради подложки не приходится. /// /// Слой не участвует в выборе курсором: щелчки должны доставаться построениям, /// а не картинке под ними. /// </remarks> public sealed class UnderlayLayer : Control { private readonly Dictionary<UnderlayId, Bitmap?> _bitmaps = []; public static readonly StyledProperty<IReadOnlyList<UnderlayImage>> ImagesProperty = AvaloniaProperty.Register<UnderlayLayer, IReadOnlyList<UnderlayImage>>( nameof(Images), Array.Empty<UnderlayImage>()); public static readonly StyledProperty<ViewportCamera?> CameraProperty = AvaloniaProperty.Register<UnderlayLayer, ViewportCamera?>(nameof(Camera)); static UnderlayLayer() { AffectsRender<UnderlayLayer>(ImagesProperty, CameraProperty); } public UnderlayLayer() => IsHitTestVisible = false; public IReadOnlyList<UnderlayImage> Images { get => GetValue(ImagesProperty); set => SetValue(ImagesProperty, value); } public ViewportCamera? Camera { get => GetValue(CameraProperty); set => SetValue(CameraProperty, value); } public override void Render(DrawingContext context) { base.Render(context); var camera = Camera; if (camera is null || Images.Count == 0) return; var surface = new Vector2((float)Bounds.Width, (float)Bounds.Height); var scale = camera.Scale; foreach (var image in Images.Where(item => item.IsVisible).OrderBy(item => item.Order)) { var bitmap = Resolve(image); if (bitmap is null) continue; // Масштаб показа задан и до калибровки: некалиброванную подложку // тоже нужно видеть — по ней калибровку и выполняют. var unitsPerPixel = image.UnitsPerPixel; var topLeft = camera.ToScreen(image.Origin, surface); var width = image.PixelWidth * unitsPerPixel * scale; var height = image.PixelHeight * unitsPerPixel * scale; if (width < 1 || height < 1) continue; var destination = new Rect(topLeft.X, topLeft.Y, width, height); using (context.PushOpacity(image.Opacity)) { if (Math.Abs(image.RotationDegrees) < 1e-6) { context.DrawImage(bitmap, destination); continue; } // Поворот выполняется вокруг левого верхнего угла изображения: // именно этот угол пользователь ставит на чертёж. var rotation = Matrix.CreateRotation(-image.RotationDegrees * Math.PI / 180.0); var toOrigin = Matrix.CreateTranslation(-topLeft.X, -topLeft.Y); var back = Matrix.CreateTranslation(topLeft.X, topLeft.Y); using (context.PushTransform(toOrigin * rotation * back)) context.DrawImage(bitmap, destination); } } } private Bitmap? Resolve(UnderlayImage image) { if (_bitmaps.TryGetValue(image.Id, out var cached)) return cached; Bitmap? bitmap; try { using var stream = new MemoryStream(image.Content); bitmap = new Bitmap(stream); } catch (Exception) { // Содержимое не распознано как изображение: подложка молча // пропускается, разрушать из-за неё работу с чертежом незачем. bitmap = null; } _bitmaps[image.Id] = bitmap; return bitmap; } /// <summary>Сбрасывает разобранные изображения при смене документа.</summary> public void Forget() { foreach (var bitmap in _bitmaps.Values) bitmap?.Dispose(); _bitmaps.Clear(); } }