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

EditorForm.Render.cs

455 строк · 18,673 байт · модуль 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        // ===== Final image rendering =====
 24
 25        private Bitmap RenderFinalImage()
 26        {
 27            // Debug: log annotation counts
 28            System.Diagnostics.Debug.WriteLine(string.Format(
 29                "RenderFinalImage: Arrows={0}, Markers={1}, TextBlocks={2}, Bubbles={3}, Highlights={4}",
 30                canvas.Arrows.Count, canvas.Markers.Count, canvas.TextBlocks.Count,
 31                canvas.CommentBubbles.Count, canvas.Highlights.Count));
 32
 33            // Use crop rect for final image
 34            var finalBounds = cropRect;
 35            var final = new Bitmap(finalBounds.Width, finalBounds.Height, PixelFormat.Format32bppArgb);
 36
 37            using (var g = Graphics.FromImage(final))
 38            {
 39                g.SmoothingMode = SmoothingMode.AntiAlias;
 40                g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;
 41                g.InterpolationMode = InterpolationMode.HighQualityBicubic;
 42
 43                // Translate to crop origin
 44                g.TranslateTransform(-finalBounds.X, -finalBounds.Y);
 45
 46                // Draw base - if highlights exist, show blurred/dimmed outside
 47                if (canvas.Highlights.Count > 0)
 48                {
 49                    // Check if any highlight has DimDisabled (no dimming at all)
 50                    bool anyDimDisabled = false;
 51                    foreach (var hl in canvas.Highlights)
 52                    {
 53                        if (hl.DimDisabled)
 54                        {
 55                            anyDimDisabled = true;
 56                            break;
 57                        }
 58                    }
 59
 60                    // Check for outside effects first
 61                    bool hasOutsideEffect = false;
 62                    HighlightRect outsideEffectHl = null;
 63                    foreach (var hl in canvas.Highlights)
 64                    {
 65                        var layer = canvas.GetEffectLayer(hl.Id);
 66                        if (layer != null && (layer.Type == EffectType.BlurOutside || layer.Type == EffectType.MotionBlurOutside))
 67                        {
 68                            hasOutsideEffect = true;
 69                            outsideEffectHl = hl;
 70                            break;
 71                        }
 72                    }
 73
 74                    if (anyDimDisabled)
 75                    {
 76                        // DimDisabled - draw original image as base (no dimming)
 77                        g.DrawImageUnscaled(captured, 0, 0);
 78                    }
 79                    else if (hasOutsideEffect && outsideEffectHl != null)
 80                    {
 81                        // Draw outside effect as base (already contains blur + dim)
 82                        var layer = canvas.GetEffectLayer(outsideEffectHl.Id);
 83                        var effectBitmap = layer.GetCachedResult(captured);
 84                        if (effectBitmap != null)
 85                            g.DrawImageUnscaled(effectBitmap, 0, 0);
 86                        else
 87                            g.DrawImageUnscaled(blurredBitmap, 0, 0);
 88                    }
 89                    else
 90                    {
 91                        // Standard dimmed background
 92                        g.DrawImageUnscaled(blurredBitmap, 0, 0);
 93                    }
 94
 95                    // Draw highlight areas with their effects
 96                    foreach (var hl in canvas.Highlights)
 97                    {
 98                        var layer = canvas.GetEffectLayer(hl.Id);
 99
100                        // Skip outside effects - already drawn as base
101                        if (layer != null && (layer.Type == EffectType.BlurOutside || layer.Type == EffectType.MotionBlurOutside))
102                            continue;
103
104                        g.SetClip(hl.Rect);
105
106                        if (layer != null && layer.Type != EffectType.None)
107                        {
108                            // Inside effect - draw effect bitmap
109                            var effectBitmap = layer.GetCachedResult(captured);
110                            if (effectBitmap != null)
111                                g.DrawImageUnscaled(effectBitmap, 0, 0);
112                            else
113                                g.DrawImageUnscaled(captured, 0, 0);
114                        }
115                        else
116                        {
117                            // No effect - draw original (clear highlight area)
118                            g.DrawImageUnscaled(captured, 0, 0);
119                        }
120                    }
121                    g.ResetClip();
122
123                    // Draw highlight borders (only if global setting enabled)
124                    if (Settings.ShowHighlightBorder)
125                    {
126                        foreach (var hl in canvas.Highlights)
127                        {
128                            // Shadow
129                            using (var shadowPen = new Pen(Color.FromArgb(60, 0, 0, 0), Settings.HighlightBorderWidth + 2))
130                            {
131                                var shadowRect = new Rectangle(hl.Rect.X + 2, hl.Rect.Y + 2, hl.Rect.Width, hl.Rect.Height);
132                                g.DrawRectangle(shadowPen, shadowRect);
133                            }
134                            using (var pen = new Pen(Settings.HighlightBorderColor, Settings.HighlightBorderWidth))
135                            {
136                                g.DrawRectangle(pen, hl.Rect);
137                            }
138                        }
139                    }
140                }
141                else
142                {
143                    g.DrawImageUnscaled(captured, 0, 0);
144                }
145
146                // Draw arrows
147                foreach (var arr in canvas.Arrows)
148                {
149                    DrawArrowFinal(g, arr);
150                }
151
152                // Draw number markers
153                foreach (var mk in canvas.Markers)
154                {
155                    DrawMarkerFinal(g, mk);
156                }
157
158                // Draw text blocks
159                foreach (var tb in canvas.TextBlocks)
160                {
161                    DrawTextBlockFinal(g, tb);
162                }
163
164                // Draw comment bubbles
165                foreach (var bubble in canvas.CommentBubbles)
166                {
167                    DrawBubbleFinal(g, bubble);
168                }
169            }
170            return final;
171        }
172
173        private void DrawArrowFinal(Graphics g, ArrowAnnotation arr)
174        {
175            // Arrow head parameters (same as in AnnotationCanvas)
176            float headLen = 20f;
177            float headWidth = 10f;
178
179            // Get tangent at end point for arrow head direction
180            var tangent = arr.GetBezierTangent(1.0f);
181
182            // Calculate arrow head points
183            var endPt = new PointF(arr.End.X, arr.End.Y);
184            var headBase = new PointF(endPt.X - tangent.X * headLen, endPt.Y - tangent.Y * headLen);
185            var perpX = -tangent.Y * headWidth;
186            var perpY = tangent.X * headWidth;
187            var headLeft = new PointF(headBase.X + perpX, headBase.Y + perpY);
188            var headRight = new PointF(headBase.X - perpX, headBase.Y - perpY);
189
190            // Shadow for curve
191            using (var shadowPen = new Pen(Color.FromArgb(Settings.ShadowAlpha, 0, 0, 0), arr.Width + 2))
192            {
193                shadowPen.StartCap = LineCap.Round;
194                shadowPen.EndCap = LineCap.Round;
195                g.DrawBezier(shadowPen,
196                    new PointF(arr.Start.X + Settings.ShadowOffset, arr.Start.Y + Settings.ShadowOffset),
197                    new PointF(arr.Control1.X + Settings.ShadowOffset, arr.Control1.Y + Settings.ShadowOffset),
198                    new PointF(arr.Control2.X + Settings.ShadowOffset, arr.Control2.Y + Settings.ShadowOffset),
199                    new PointF(headBase.X + Settings.ShadowOffset, headBase.Y + Settings.ShadowOffset));
200            }
201
202            // Shadow for arrow head
203            using (var shadowBrush = new SolidBrush(Color.FromArgb(Settings.ShadowAlpha, 0, 0, 0)))
204            {
205                var shadowHead = new PointF[] {
206                    new PointF(endPt.X + Settings.ShadowOffset, endPt.Y + Settings.ShadowOffset),
207                    new PointF(headLeft.X + Settings.ShadowOffset, headLeft.Y + Settings.ShadowOffset),
208                    new PointF(headRight.X + Settings.ShadowOffset, headRight.Y + Settings.ShadowOffset)
209                };
210                g.FillPolygon(shadowBrush, shadowHead);
211            }
212
213            // Main curve (without built-in arrow cap - we draw custom head)
214            using (var pen = new Pen(arr.Color, arr.Width))
215            {
216                pen.StartCap = LineCap.Round;
217                pen.EndCap = LineCap.Round;
218                g.DrawBezier(pen, arr.Start, arr.Control1, arr.Control2, new Point((int)headBase.X, (int)headBase.Y));
219            }
220
221            // Custom arrow head (filled triangle)
222            using (var brush = new SolidBrush(arr.Color))
223            {
224                var headPoints = new PointF[] { endPt, headLeft, headRight };
225                g.FillPolygon(brush, headPoints);
226            }
227        }
228
229        private void DrawMarkerFinal(Graphics g, NumberMarker marker)
230        {
231            // Use marker's individual font size
232            float fontSize = marker.FontSize;
233            string text = marker.Number.ToString();
234
235            using (var font = new Font(Settings.MarkerFont, fontSize, FontStyle.Bold))
236            {
237                var textSize = g.MeasureString(text, font);
238                float x = marker.Location.X - textSize.Width / 2;
239                float y = marker.Location.Y - textSize.Height / 2;
240
241                // Shadow (offset)
242                float shadowOffset = Math.Max(2, fontSize / 10);
243                using (var shadowBrush = new SolidBrush(Color.FromArgb(100, 0, 0, 0)))
244                {
245                    g.DrawString(text, font, shadowBrush, x + shadowOffset, y + shadowOffset);
246                }
247
248                // Black outline (draw text multiple times with offset)
249                float outlineWidth = Math.Max(1.5f, fontSize / 15);
250                using (var outlineBrush = new SolidBrush(Color.Black))
251                {
252                    for (float ox = -outlineWidth; ox <= outlineWidth; ox += outlineWidth)
253                    {
254                        for (float oy = -outlineWidth; oy <= outlineWidth; oy += outlineWidth)
255                        {
256                            if (ox != 0 || oy != 0)
257                                g.DrawString(text, font, outlineBrush, x + ox, y + oy);
258                        }
259                    }
260                }
261
262                // Main text
263                using (var brush = new SolidBrush(Settings.MarkerTextColor))
264                {
265                    g.DrawString(text, font, brush, x, y);
266                }
267            }
268        }
269
270        private void DrawTextBlockFinal(Graphics g, TextBlock tb)
271        {
272            // Check if 3D rotation is applied
273            bool has3DRotation = Math.Abs(tb.RotationX) > 0.1f || Math.Abs(tb.RotationY) > 0.1f;
274
275            if (has3DRotation)
276            {
277                DrawTextBlockFinal3D(g, tb);
278                return;
279            }
280
281            // Standard 2D drawing with shadow
282            DrawTextBlockFinalFlat(g, tb);
283        }
284
285        private void DrawTextBlockFinalFlat(Graphics g, TextBlock tb)
286        {
287            // Realistic shadow with soft edges and alpha falloff
288            if (tb.ShadowOffset > 0)
289            {
290                var shadowOffset = tb.GetShadowOffset();
291                float shadowX = tb.Location.X + shadowOffset.X;
292                float shadowY = tb.Location.Y + shadowOffset.Y;
293
294                int baseAlpha = tb.ShadowColor.A;
295                float distanceFactor = 1f / (1f + tb.ShadowOffset * 0.05f);
296                int coreAlpha = (int)(baseAlpha * distanceFactor * 0.7f);
297                float blurRadius = tb.ShadowOffset * 0.4f;
298
299                int numRings = Math.Min(6, 2 + tb.ShadowOffset / 8);
300                int pointsPerRing = 8;
301
302                for (int ring = numRings; ring >= 1; ring--)
303                {
304                    float ringRadius = blurRadius * ring / numRings;
305                    float ringFactor = 1f - (float)ring / (numRings + 1);
306                    ringFactor = ringFactor * ringFactor;
307                    int ringAlpha = (int)(coreAlpha * 0.3f * ringFactor);
308
309                    if (ringAlpha < 1) continue;
310
311                    using (var brush = new SolidBrush(Color.FromArgb(ringAlpha, tb.ShadowColor)))
312                    {
313                        for (int p = 0; p < pointsPerRing; p++)
314                        {
315                            double angle = 2 * Math.PI * p / pointsPerRing;
316                            float dx = (float)(ringRadius * Math.Cos(angle));
317                            float dy = (float)(ringRadius * Math.Sin(angle));
318                            g.DrawString(tb.Text, tb.TextFont, brush, shadowX + dx, shadowY + dy);
319                        }
320                    }
321                }
322
323                if (coreAlpha > 0)
324                {
325                    using (var brush = new SolidBrush(Color.FromArgb(coreAlpha, tb.ShadowColor)))
326                    {
327                        g.DrawString(tb.Text, tb.TextFont, brush, shadowX, shadowY);
328                    }
329                }
330            }
331
332            using (var brush = new SolidBrush(tb.TextColor))
333            {
334                g.DrawString(tb.Text, tb.TextFont, brush, tb.Location);
335            }
336        }
337
338        private void DrawTextBlockFinal3D(Graphics g, TextBlock tb)
339        {
340            // Measure text to get center point
341            var textSize = g.MeasureString(tb.Text, tb.TextFont);
342            float centerX = tb.Location.X + textSize.Width / 2;
343            float centerY = tb.Location.Y + textSize.Height / 2;
344
345            // Convert rotation angles to radians
346            float rotXRad = tb.RotationX * (float)Math.PI / 180f;
347            float rotYRad = tb.RotationY * (float)Math.PI / 180f;
348
349            float cosX = (float)Math.Cos(rotXRad);
350            float cosY = (float)Math.Cos(rotYRad);
351
352            var oldTransform = g.Transform.Clone();
353            var matrix = new Matrix();
354            matrix.Translate(centerX, centerY);
355
356            float shearX = (float)Math.Sin(rotYRad) * 0.5f;
357            float scaleX = Math.Max(0.3f, cosY);
358            float shearY = (float)Math.Sin(rotXRad) * 0.5f;
359            float scaleY = Math.Max(0.3f, cosX);
360
361            matrix.Scale(scaleX, scaleY);
362            matrix.Shear(shearX, shearY);
363            matrix.Translate(-centerX, -centerY);
364            g.Transform = matrix;
365
366            // Draw shadow with transform
367            if (tb.ShadowOffset > 0)
368            {
369                var shadowOffset = tb.GetShadowOffset();
370                float shadowX = tb.Location.X + shadowOffset.X;
371                float shadowY = tb.Location.Y + shadowOffset.Y;
372
373                int baseAlpha = tb.ShadowColor.A;
374                float distanceFactor = 1f / (1f + tb.ShadowOffset * 0.05f);
375                int coreAlpha = (int)(baseAlpha * distanceFactor * 0.7f);
376                float blurRadius = tb.ShadowOffset * 0.4f;
377
378                int numRings = Math.Min(6, 2 + tb.ShadowOffset / 8);
379                int pointsPerRing = 8;
380
381                for (int ring = numRings; ring >= 1; ring--)
382                {
383                    float ringRadius = blurRadius * ring / numRings;
384                    float ringFactor = 1f - (float)ring / (numRings + 1);
385                    ringFactor = ringFactor * ringFactor;
386                    int ringAlpha = (int)(coreAlpha * 0.3f * ringFactor);
387
388                    if (ringAlpha < 1) continue;
389
390                    using (var brush = new SolidBrush(Color.FromArgb(ringAlpha, tb.ShadowColor)))
391                    {
392                        for (int p = 0; p < pointsPerRing; p++)
393                        {
394                            double angle = 2 * Math.PI * p / pointsPerRing;
395                            float dx = (float)(ringRadius * Math.Cos(angle));
396                            float dy = (float)(ringRadius * Math.Sin(angle));
397                            g.DrawString(tb.Text, tb.TextFont, brush, shadowX + dx, shadowY + dy);
398                        }
399                    }
400                }
401
402                if (coreAlpha > 0)
403                {
404                    using (var brush = new SolidBrush(Color.FromArgb(coreAlpha, tb.ShadowColor)))
405                    {
406                        g.DrawString(tb.Text, tb.TextFont, brush, shadowX, shadowY);
407                    }
408                }
409            }
410
411            using (var brush = new SolidBrush(tb.TextColor))
412            {
413                g.DrawString(tb.Text, tb.TextFont, brush, tb.Location);
414            }
415
416            g.Transform = oldTransform;
417            oldTransform.Dispose();
418            matrix.Dispose();
419        }
420
421        private void DrawBubbleFinal(Graphics g, CommentBubble bubble)
422        {
423            var path = bubble.GetBubblePath(g);
424            if (path == null) return;
425
426            // Shadow
427            var shadowMatrix = new Matrix();
428            shadowMatrix.Translate(3, 3);
429            var shadowPath = (GraphicsPath)path.Clone();
430            shadowPath.Transform(shadowMatrix);
431            using (var shadowBrush = new SolidBrush(Color.FromArgb(60, 0, 0, 0)))
432            {
433                g.FillPath(shadowBrush, shadowPath);
434            }
435            shadowPath.Dispose();
436
437            using (var brush = new SolidBrush(bubble.FillColor))
438            {
439                g.FillPath(brush, path);
440            }
441            using (var pen = new Pen(bubble.BorderColor, bubble.BorderWidth))
442            {
443                g.DrawPath(pen, path);
444            }
445
446            var textBounds = bubble.GetTextBounds(g);
447            using (var brush = new SolidBrush(bubble.TextColor))
448            {
449                g.DrawString(bubble.Text, bubble.TextFont, brush, textBounds.Location);
450            }
451
452            path.Dispose();
453        }
454    }
455}