windowcapture
исходный код / UI/EditorForm.VideoPaint.cs

EditorForm.VideoPaint.cs

303 строк · 13,573 байт · модуль UI
  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        // ===== Video overlay painting =====
 24
 25        // ===== Video overlay painting =====
 26
 27        private void VideoOverlay_Paint(object sender, PaintEventArgs e)
 28        {
 29            // UI is drawn via CreateGraphics in timer tick (over video)
 30            // This handler only used if videoOverlay panel exists (legacy)
 31            if (videoOverlay == null) return;
 32            var g = e.Graphics;
 33            g.SmoothingMode = SmoothingMode.AntiAlias;
 34            PaintGlassButtons(g);
 35            PaintVideoControls(g);
 36            PaintFileNameIndicator(g);
 37
 38            // Volume indicator
 39            PaintVolumeIndicator(g);
 40        }
 41
 42        private string FormatTime(double seconds)
 43        {
 44            if (double.IsNaN(seconds) || seconds < 0) seconds = 0;
 45            int s = (int)seconds;
 46            int m = s / 60;
 47            s = s % 60;
 48            int h = m / 60;
 49            m = m % 60;
 50            if (h > 0)
 51                return h + ":" + m.ToString("D2") + ":" + s.ToString("D2");
 52            return m + ":" + s.ToString("D2");
 53        }
 54
 55        private Rectangle GetVideoSeekBarRect(Control panel)
 56        {
 57            int w = panel.Width;
 58            int h = panel.Height;
 59            int barY = h - VideoControlBarH;
 60            int seekX = 50;
 61            int seekW = w - 50 - 200;
 62            if (seekW < 60) seekW = 60;
 63            return new Rectangle(seekX, barY + 7, seekW, 36);
 64        }
 65
 66        private Rectangle GetVideoVolBarRect(Control panel)
 67        {
 68            int w = panel.Width;
 69            int h = panel.Height;
 70            int barY = h - VideoControlBarH;
 71            return new Rectangle(w - 90, barY + 17, 60, 16);
 72        }
 73
 74        private void PaintVideoControls(Graphics g)
 75        {
 76            if ((!isVideoFile && !isAudioFile) || isAnimatedGif || videoControlsAlpha <= 0.01f) return;
 77            var panel = VideoPanel ?? scrollContainer;
 78            // Apply Bezier S-curve for smooth non-linear fade
 79            float alpha = videoControlsAlpha * videoControlsAlpha * (3f - 2f * videoControlsAlpha);
 80            int w = panel.Width;
 81            int h = panel.Height;
 82            int barY = h - VideoControlBarH;
 83
 84            // Auto-color based on tint brightness
 85            Color tint = Settings.BlurTintColor;
 86            float br = ColorHelper.GetBrightness(tint);
 87            bool isDark = br < 0.45f;
 88            Color textColor = isDark
 89                ? Color.FromArgb((int)(alpha * 210), 210, 218, 235)
 90                : Color.FromArgb((int)(alpha * 210), 25, 25, 35);
 91            Color dimColor = isDark
 92                ? Color.FromArgb((int)(alpha * 100), 180, 190, 210)
 93                : Color.FromArgb((int)(alpha * 100), 50, 50, 65);
 94            Color edgeColor = isDark
 95                ? Color.FromArgb((int)(alpha * 50), 200, 210, 230)
 96                : Color.FromArgb((int)(alpha * 50), 80, 80, 100);
 97            Color fillColor = isDark
 98                ? Color.FromArgb((int)(alpha * 20), 140, 155, 180)
 99                : Color.FromArgb((int)(alpha * 20), 40, 40, 55);
100            Color accentColor = Color.FromArgb((int)(alpha * 200), 120, 160, 230);
101            Color trackColor = isDark
102                ? Color.FromArgb((int)(alpha * 35), 160, 175, 200)
103                : Color.FromArgb((int)(alpha * 35), 80, 80, 100);
104
105            // Get cursor position for proximity glow
106            Point cursorPt = panel.PointToClient(Cursor.Position);
107
108            // Bar background — gradient fade from transparent to semi-opaque
109            for (int row = 0; row < VideoControlBarH; row++)
110            {
111                float t = (float)row / VideoControlBarH;
112                int a = (int)(alpha * 30 * t * t);
113                using (var pen = new Pen(Color.FromArgb(a, 0, 0, 0)))
114                    g.DrawLine(pen, 0, barY + row, w, barY + row);
115            }
116
117            // ===== Play/Pause button =====
118            int btnX = 14, btnCY = barY + VideoControlBarH / 2, btnSize = 20;
119            float playGlow = CalcElementGlow(cursorPt, new PointF(btnX + btnSize / 2f, btnCY), 150f);
120            playGlow = Math.Max(playGlow, alpha * 0.5f); // minimum visibility
121
122            if (videoPlaying)
123            {
124                int bw = 3, gap = 5;
125                int lx = btnX + (btnSize - bw * 2 - gap) / 2;
126                using (var brush = new SolidBrush(Color.FromArgb((int)(playGlow * 220), textColor)))
127                {
128                    g.FillRectangle(brush, lx, btnCY - 8, bw, 16);
129                    g.FillRectangle(brush, lx + bw + gap, btnCY - 8, bw, 16);
130                }
131            }
132            else
133            {
134                var pts = new PointF[]
135                {
136                    new PointF(btnX + 3, btnCY - 9),
137                    new PointF(btnX + btnSize - 2, btnCY),
138                    new PointF(btnX + 3, btnCY + 9)
139                };
140                using (var brush = new SolidBrush(Color.FromArgb((int)(playGlow * 220), textColor)))
141                    g.FillPolygon(brush, pts);
142            }
143
144            // ===== Seek bar (Spectrogram Style — polygon fill like audio player) =====
145            var seekRect = GetVideoSeekBarRect(panel);
146            int centerY = seekRect.Y + seekRect.Height / 2;
147
148            // Waveform (real peaks or fallback sine)
149            float[] peaks = waveformPeaks;
150            int peakCount = peaks != null ? peaks.Length : 0;
151
152            float displayProgress = seekBarProgress;
153            int fillW = (int)(seekRect.Width * Math.Max(0f, Math.Min(1f, displayProgress)));
154            int headX = seekRect.X + fillW;
155            int maxHalfH = seekRect.Height / 2 - 2;
156
157            // Build polygon points for the waveform (top half + bottom half in reverse)
158            int sw = seekRect.Width;
159            int polyCount = sw * 2;
160            if (polyCount < 4) polyCount = 4;
161            var polyPts = new PointF[polyCount];
162
163            // Energy pulse near playhead (like audio player)
164            float effectRange = sw * 0.2f;
165            float energyBoost = ovlSeeking ? 1.6f : (videoPlaying ? 1.2f : 0.8f);
166
167            for (int i = 0; i < sw; i++)
168            {
169                int x = seekRect.X + i;
170                float t = (float)i / sw;
171                float peak;
172                if (peakCount > 0)
173                {
174                    int idx = (int)(t * (peakCount - 1));
175                    peak = peaks[Math.Min(idx, peakCount - 1)];
176                }
177                else
178                    peak = 0.15f + 0.25f * (float)Math.Sin(x * 0.04f);
179
180                // Local energy pulse near playhead
181                float dist = Math.Abs(x - headX);
182                float localFactor = (dist < effectRange) ? (float)Math.Pow(1.0 - dist / effectRange, 2) : 0f;
183                float pulse = 1.0f + localFactor * energyBoost;
184
185                float hh = peak * maxHalfH * pulse;
186                // Soft-clipping
187                if (hh > maxHalfH * 0.75f)
188                {
189                    float overflow = hh - maxHalfH * 0.75f;
190                    hh = maxHalfH * 0.75f + maxHalfH * 0.25f * (float)Math.Atan(overflow / (maxHalfH * 0.25f)) / (float)(Math.PI / 2);
191                }
192                if (hh < 1) hh = 1;
193
194                polyPts[i] = new PointF(x, centerY - hh);
195                polyPts[polyCount - 1 - i] = new PointF(x, centerY + hh);
196            }
197
198            // Draw played + unplayed with clip regions
199            using (var brPlayed = new SolidBrush(Color.FromArgb((int)(alpha * 220), accentColor)))
200            using (var brUnplayed = new SolidBrush(Color.FromArgb((int)(alpha * 60), trackColor)))
201            {
202                var oldClip = g.Clip;
203                g.SetClip(new Rectangle(seekRect.X, seekRect.Y, fillW, seekRect.Height));
204                g.FillPolygon(brPlayed, polyPts);
205                g.SetClip(new Rectangle(headX, seekRect.Y, seekRect.Width - fillW, seekRect.Height));
206                g.FillPolygon(brUnplayed, polyPts);
207                g.Clip = oldClip;
208            }
209
210            // Head line
211            using (var pen = new Pen(Color.FromArgb((int)(alpha * 200), textColor), 1.5f))
212                g.DrawLine(pen, headX, seekRect.Y + 2, headX, seekRect.Bottom - 2);
213
214            // Seek thumb (larger when dragging)
215            float seekGlow = CalcElementGlow(cursorPt, new PointF(headX, centerY), 100f);
216            seekGlow = Math.Max(seekGlow, ovlSeeking ? 1f : alpha * 0.3f);
217            int thumbR = (int)(3 + seekGlow * 4);
218            using (var brush = new SolidBrush(Color.FromArgb((int)(seekGlow * 240), 200, 215, 245)))
219                g.FillEllipse(brush, headX - thumbR, centerY - thumbR, thumbR * 2, thumbR * 2);
220            using (var pen = new Pen(Color.FromArgb((int)(seekGlow * 60), 160, 180, 220), 1f))
221                g.DrawEllipse(pen, headX - thumbR, centerY - thumbR, thumbR * 2, thumbR * 2);
222
223            // ===== Time display =====
224            string timeText = FormatTime(videoCurrentTime) + " / " + FormatTime(videoDuration);
225            using (var font = new Font("Segoe UI", 11f, FontStyle.Regular, GraphicsUnit.Pixel))
226            {
227                var sz = g.MeasureString(timeText, font);
228                float tx = seekRect.Right + 12;
229                float ty = barY + (VideoControlBarH - sz.Height) / 2f;
230                float timeGlow = CalcElementGlow(cursorPt, new PointF(tx + sz.Width / 2f, ty + sz.Height / 2f), 120f);
231                timeGlow = Math.Max(timeGlow, alpha * 0.4f);
232                g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;
233                using (var brush = new SolidBrush(Color.FromArgb((int)(timeGlow * 150), dimColor)))
234                    g.DrawString(timeText, font, brush, tx, ty);
235            }
236
237            // ===== Volume icon =====
238            int volIconX = w - 118;
239            int volIconCY = barY + VideoControlBarH / 2;
240            float volGlow = CalcElementGlow(cursorPt, new PointF(volIconX + 8, volIconCY), 130f);
241            volGlow = Math.Max(volGlow, alpha * 0.4f);
242
243            Color volColor = videoMuted
244                ? Color.FromArgb((int)(volGlow * 120), dimColor)
245                : Color.FromArgb((int)(volGlow * 200), textColor);
246            int viy = volIconCY - 6;
247            using (var pen = new Pen(volColor, 1.2f))
248            {
249                g.DrawLine(pen, volIconX, viy + 4, volIconX + 4, viy + 4);
250                g.DrawLine(pen, volIconX + 4, viy + 4, volIconX + 8, viy);
251                g.DrawLine(pen, volIconX + 8, viy, volIconX + 8, viy + 12);
252                g.DrawLine(pen, volIconX + 8, viy + 12, volIconX + 4, viy + 8);
253                g.DrawLine(pen, volIconX + 4, viy + 8, volIconX, viy + 8);
254                g.DrawLine(pen, volIconX, viy + 8, volIconX, viy + 4);
255
256                if (videoMuted)
257                {
258                    using (var xPen = new Pen(Color.FromArgb((int)(volGlow * 160), 220, 100, 100), 1.2f))
259                    {
260                        g.DrawLine(xPen, volIconX + 11, viy + 3, volIconX + 17, viy + 9);
261                        g.DrawLine(xPen, volIconX + 17, viy + 3, volIconX + 11, viy + 9);
262                    }
263                }
264                else
265                {
266                    using (var wPen = new Pen(Color.FromArgb((int)(volGlow * 80), volColor), 1.0f))
267                    {
268                        g.DrawArc(wPen, volIconX + 10, viy + 1, 8, 10, -45, 90);
269                        if (videoVolume > 0.4f)
270                            g.DrawArc(wPen, volIconX + 13, viy - 1, 10, 14, -50, 100);
271                    }
272                }
273            }
274
275            // ===== Volume bar =====
276            var volRect = GetVideoVolBarRect(panel);
277            int volTrackY = volRect.Y + volRect.Height / 2 - 2;
278            float volBarGlow = CalcElementGlow(cursorPt, new PointF(volRect.X + volRect.Width / 2f, volRect.Y + volRect.Height / 2f), 100f);
279            volBarGlow = Math.Max(volBarGlow, alpha * 0.3f);
280            float vol = videoMuted ? 0f : videoVolume;
281
282            using (var brush = new SolidBrush(Color.FromArgb((int)(volBarGlow * 50), trackColor)))
283            {
284                using (var path = CreateRoundRectPath(volRect.X, volTrackY, volRect.Width, 4, 2))
285                    g.FillPath(brush, path);
286            }
287            int volFillW = (int)(volRect.Width * vol);
288            if (volFillW > 2)
289            {
290                using (var brush = new SolidBrush(Color.FromArgb((int)(volBarGlow * 200), accentColor)))
291                {
292                    using (var path = CreateRoundRectPath(volRect.X, volTrackY, volFillW, 4, 2))
293                        g.FillPath(brush, path);
294                }
295            }
296            int vThumbX = volRect.X + volFillW;
297            int vThumbCY = volRect.Y + volRect.Height / 2;
298            int vThumbR = (int)(3 + volBarGlow * 3);
299            using (var brush = new SolidBrush(Color.FromArgb((int)(volBarGlow * 220), 200, 215, 245)))
300                g.FillEllipse(brush, vThumbX - vThumbR, vThumbCY - vThumbR, vThumbR * 2, vThumbR * 2);
301        }
302    }
303}