1using System; 2using System.Collections.Generic; 3using System.Drawing; 4using System.Drawing.Drawing2D; 5using System.Drawing.Imaging; 6using System.Drawing.Text; 7using System.IO; 8using System.Linq; 9using System.Runtime.InteropServices; 10using System.Windows.Forms; 11using WindowCapture.App; 12using WindowCapture.Detection; 13using WindowCapture.Effects; 14using WindowCapture.Helpers; 15using WindowCapture.Integration; 16using WindowCapture.Models; 17using WindowCapture.Native; 18 19namespace WindowCapture.UI 20{ 21 public partial class EditorForm 22 { 23 // ===== Zoom, D2D, auto-detect, crop ===== 24 25 private bool IsPathRectangular(GraphicsPath path, Rectangle pathBounds) 26 { 27 var pts = path.PathPoints; 28 if (pts.Length != 4) return false; 29 30 foreach (var pt in pts) 31 { 32 bool atCorner = (Math.Abs(pt.X - pathBounds.Left) < 5 || Math.Abs(pt.X - pathBounds.Right) < 5) && 33 (Math.Abs(pt.Y - pathBounds.Top) < 5 || Math.Abs(pt.Y - pathBounds.Bottom) < 5); 34 if (!atCorner) return false; 35 } 36 return true; 37 } 38 39 private void CreateBlurredBitmap() 40 { 41 blurredBitmap = new Bitmap(bounds.Width, bounds.Height, PixelFormat.Format32bppArgb); 42 using (var g = Graphics.FromImage(blurredBitmap)) 43 { 44 g.DrawImageUnscaled(captured, 0, 0); 45 using (var dimBrush = new SolidBrush(Color.FromArgb(Settings.DimAlpha, 0, 0, 0))) 46 g.FillRectangle(dimBrush, 0, 0, bounds.Width, bounds.Height); 47 } 48 } 49 50 public void SetZoom(float level) 51 { 52 targetZoom = level; 53 PerformanceLogger.Log("SetZoom", -1, string.Format("Target: {0:F2}", level)); 54 if (!zoomTimer.Enabled) 55 { 56 canvas.BeginZoomAnimation(); 57 zoomTimer.Start(); 58 } 59 } 60 61 public void FitToWindow() 62 { 63 int maxW = scrollContainer.Width - 40; 64 int maxH = scrollContainer.Height - 40; 65 if (bounds.Width > 0 && bounds.Height > 0) 66 { 67 float scaleW = (float)maxW / bounds.Width; 68 float scaleH = (float)maxH / bounds.Height; 69 SetZoom(Math.Min(1.0f, Math.Min(scaleW, scaleH))); 70 } 71 } 72 73 public void UpdateAutoDetect(Point mousePos) 74 { 75 if (!altPressed) return; 76 77 var path = editorDetector.Detect(mousePos, 0); 78 if (path != null) 79 { 80 autoDetectedRect = Rectangle.Ceiling(path.GetBounds()); 81 path.Dispose(); 82 } 83 else 84 { 85 autoDetectedRect = Rectangle.Empty; 86 } 87 canvas.Invalidate(); 88 } 89 90 public void SetCropRect(Rectangle rect) 91 { 92 cropRect = rect; 93 if (canvas != null) canvas.Invalidate(); 94 } 95 96 // Get scaled image size (единый метод для избежания рассогласования) 97 public Size GetScaledImageSize() 98 { 99 return new Size( 100 (int)Math.Round(bounds.Width * zoomLevel), 101 (int)Math.Round(bounds.Height * zoomLevel)); 102 } 103 104 private void InitializeDirect2D() 105 { 106 // Delay init until canvas has a handle 107 if (!canvas.IsHandleCreated) 108 { 109 canvas.HandleCreated += delegate { InitializeDirect2D(); }; 110 return; 111 } 112 113 try 114 { 115 // In Static Viewport, D2D target is always viewport size 116 int vw = scrollContainer.ClientSize.Width; 117 int vh = scrollContainer.ClientSize.Height; 118 if (vw < 1) vw = 1; if (vh < 1) vh = 1; 119 120 bool ok = D2DRenderer.Initialize(canvas.Handle, vw, vh); 121 if (ok && captured != null) 122 { 123 D2DRenderer.UploadBitmap(captured); 124 canvas.SetD2DActive(true); 125 } 126 System.Diagnostics.Debug.WriteLine("D2D init: " + (ok ? "OK" : "FAILED (using GDI+ fallback)")); 127 } 128 catch (Exception ex) 129 { 130 System.Diagnostics.Debug.WriteLine("D2D init exception: " + ex.Message); 131 } 132 } 133 134 /// <summary>Re-create the D2D device after a device-loss (GPU reset / RDP / driver update). 135 /// Called by the canvas when it notices GPU rendering went unavailable mid-session.</summary> 136 public void TryRecoverD2D() 137 { 138 InitializeDirect2D(); 139 } 140 141 private Point zoomMousePos; // Screen coordinates for stable reference 142 143 public void ApplyZoom(float delta, Point canvasMousePos = default(Point)) 144 { 145 float direction = delta > 0 ? 1f : -1f; 146 float newTarget = targetZoom * (float)Math.Pow(zoomFactor, direction); 147 148 // Limit to min/max 149 newTarget = Math.Max(zoomMin, Math.Min(zoomMax, newTarget)); 150 151 if (Math.Abs(newTarget - targetZoom) < 0.0001f && !zoomTimer.Enabled) return; 152 153 targetZoom = newTarget; 154 155 // Only capture mouse position on first scroll (when animation not running) 156 if (!zoomTimer.Enabled && canvasMousePos != default(Point)) 157 { 158 zoomMousePos = canvasMousePos; 159 canvas.BeginZoomAnimation(); 160 zoomTimer.Start(); 161 } 162 else if (!zoomTimer.Enabled) 163 { 164 zoomMousePos = default(Point); 165 canvas.BeginZoomAnimation(); 166 zoomTimer.Start(); 167 } 168 } 169 170 private void ZoomTimer_Tick(object sender, EventArgs e) 171 { 172 // Exponential interpolation for smoother feel 173 float diff = targetZoom - zoomLevel; 174 175 if (Math.Abs(diff) < 0.001f * zoomLevel) // scaled precision for higher zooms 176 { 177 zoomLevel = targetZoom; 178 zoomTimer.Stop(); 179 canvas.EndZoomAnimation(); 180 } 181 else 182 { 183 // Smooth interpolation proportional to current zoom 184 zoomLevel += diff * ZoomInterpolationFactor; 185 } 186 187 ApplyZoomInternal(); 188 } 189 190 private void UpdateCanvasSize() 191 { 192 canvas.Size = GetScaledImageSize(); 193 // Resize D2D render target to match canvas 194 if (D2DRenderer.IsAvailable) 195 { 196 D2DRenderer.Resize(canvas.Width, canvas.Height); 197 } 198 } 199 200 private void ApplyZoomInternal() 201 { 202 if (IsDisposed || scrollContainer == null || !scrollContainer.IsHandleCreated || scrollContainer.IsDisposed || canvas == null || canvas.IsDisposed) return; 203 204 int vw = scrollContainer.ClientSize.Width; 205 int vh = scrollContainer.ClientSize.Height; 206 if (vw < 10) vw = Width; // Fallback if not layouted yet 207 if (vh < 10) vh = Height; 208 209 // Target image size at current zoomLevel 210 float imgW = bounds.Width * zoomLevel; 211 float imgH = bounds.Height * zoomLevel; 212 213 // GLUED TO CENTER LOGIC: 214 // Calculate offsets so the center of the image always matches the center of the viewport. 215 // This eliminates the 'top-left' jump and the 'cursor drift'. 216 canvasDrawX = (vw - imgW) / 2f; 217 canvasDrawY = (vh - imgH) / 2f; 218 canvasDrawScale = zoomLevel; 219 220 // GPU Target management 221 if (D2DRenderer.IsAvailable && (D2DRenderer.TargetWidth != vw || D2DRenderer.TargetHeight != vh)) 222 { 223 D2DRenderer.Resize(vw, vh); 224 } 225 226 // UNIFIED GPU TRANSFORM 227 canvas.SetViewportTransform(canvasDrawScale, canvasDrawX, canvasDrawY); 228 229 ShowZoomIndicator(); 230 } 231 232 private float canvasDrawX = 0f; 233 private float canvasDrawY = 0f; 234 private float canvasDrawScale = 1.0f; 235 236 // Direct zoom for keyboard (без анимации для точного контроля) 237 public void ApplyZoomDirect(float delta) 238 { 239 targetZoom = Math.Max(zoomMin, Math.Min(zoomMax, zoomLevel + delta)); 240 zoomLevel = targetZoom; 241 zoomMousePos = default(Point); 242 ApplyZoomInternal(); 243 } 244 245 private void ShowZoomIndicator() 246 { 247 zoomIndicatorAlpha = 1.0f; 248 zoomLastChangeTime = DateTime.Now; 249 if (!zoomFadeTimer.Enabled) 250 zoomFadeTimer.Start(); 251 } 252 253 private void ZoomFadeTimer_Tick(object sender, EventArgs e) 254 { 255 double elapsed = (DateTime.Now - zoomLastChangeTime).TotalMilliseconds; 256 257 if (elapsed < ZoomFadeDelayMs) 258 { 259 // Still showing — keep alpha at 1 260 zoomIndicatorAlpha = 1.0f; 261 } 262 else 263 { 264 // Fading out 265 float fadeProgress = (float)(elapsed - ZoomFadeDelayMs) / ZoomFadeDurationMs; 266 zoomIndicatorAlpha = Math.Max(0f, 1.0f - fadeProgress); 267 } 268 269 if (zoomIndicatorAlpha <= 0f) 270 { 271 zoomIndicatorAlpha = 0f; 272 zoomFadeTimer.Stop(); 273 } 274 275 if (scrollContainer != null && scrollContainer.IsHandleCreated) 276 { 277 var r = new Rectangle(0, 0, 150, 80); // top-left area for indicator 278 scrollContainer.Invalidate(r); 279 280 if (canvas != null && canvas.IsHandleCreated) 281 { 282 var cr = new Rectangle(r.X - canvas.Left, r.Y - canvas.Top, r.Width, r.Height); 283 if (canvas.ClientRectangle.IntersectsWith(cr)) 284 canvas.Invalidate(cr); 285 } 286 } 287 } 288 289 private void CenterImage() 290 { 291 if (canvas == null || bounds.Width <= 0) return; 292 293 int vw = scrollContainer.ClientSize.Width; 294 int vh = scrollContainer.ClientSize.Height; 295 if (vw < 10) vw = Width; 296 if (vh < 10) vh = Height; 297 298 float imgW = bounds.Width * zoomLevel; 299 float imgH = bounds.Height * zoomLevel; 300 301 canvasDrawX = (vw - imgW) / 2f; 302 canvasDrawY = (vh - imgH) / 2f; 303 canvasDrawScale = zoomLevel; 304 305 // Keep D2D render target in sync with viewport size 306 if (D2DRenderer.IsAvailable && (D2DRenderer.TargetWidth != vw || D2DRenderer.TargetHeight != vh)) 307 D2DRenderer.Resize(vw, vh); 308 309 canvas.SetViewportTransform(canvasDrawScale, canvasDrawX, canvasDrawY); 310 } 311 } 312}