windowcapture
исходный код / Effects/ImageEffects.cs

ImageEffects.cs

605 строк · 25,395 байт · модуль Effects
  1using System;
  2using System.Drawing;
  3using System.Drawing.Drawing2D;
  4using System.Drawing.Imaging;
  5using WindowCapture.Models;
  6
  7namespace WindowCapture.Effects
  8{
  9    public static class ImageEffects
 10    {
 11        /// <summary>
 12        /// Apply Gaussian blur to a rectangular area of the bitmap.
 13        /// </summary>
 14        public static void ApplyBlur(Bitmap bmp, Rectangle area, int radius)
 15        {
 16            if (radius < 1) return;
 17
 18            // Clamp area to bitmap bounds
 19            area = Rectangle.Intersect(area, new Rectangle(0, 0, bmp.Width, bmp.Height));
 20            if (area.Width <= 0 || area.Height <= 0) return;
 21
 22            // Apply Gaussian blur with cosine-based intensity curve
 23            // This gives more natural progression: subtle at low values, stronger at high
 24            ApplyGaussianBlur(bmp, area, radius);
 25        }
 26
 27        /// <summary>
 28        /// Gaussian blur with proper weighted kernel for smooth, natural-looking blur.
 29        /// </summary>
 30        private static void ApplyGaussianBlur(Bitmap bmp, Rectangle area, int radius)
 31        {
 32            if (radius < 1) return;
 33
 34            // Convert radius to sigma using cosine curve for more natural feel
 35            // At radius=1: very subtle, at radius=10: strong but not extreme
 36            // Cosine curve: slow start, faster in middle, slows at end
 37            float t = (radius - 1) / 9.0f; // normalize to 0-1
 38            float cosineT = (float)(1.0 - Math.Cos(t * Math.PI)) / 2.0f; // S-curve
 39            float sigma = 0.5f + cosineT * 8.0f; // range 0.5 to 8.5
 40
 41            // Generate 1D Gaussian kernel
 42            int kernelSize = (int)Math.Ceiling(sigma * 3) * 2 + 1;
 43            if (kernelSize < 3) kernelSize = 3;
 44            float[] kernel = new float[kernelSize];
 45            float sum = 0;
 46            int half = kernelSize / 2;
 47
 48            for (int i = 0; i < kernelSize; i++)
 49            {
 50                float x = i - half;
 51                kernel[i] = (float)Math.Exp(-(x * x) / (2 * sigma * sigma));
 52                sum += kernel[i];
 53            }
 54
 55            // Normalize kernel
 56            for (int i = 0; i < kernelSize; i++)
 57                kernel[i] /= sum;
 58
 59            var data = bmp.LockBits(area, ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);
 60            try
 61            {
 62            int stride = data.Stride;
 63            int bytes = Math.Abs(stride) * area.Height;
 64            byte[] pixels = new byte[bytes];
 65            byte[] temp = new byte[bytes];
 66
 67            System.Runtime.InteropServices.Marshal.Copy(data.Scan0, pixels, 0, bytes);
 68
 69            // Horizontal pass with Gaussian weights
 70            for (int y = 0; y < area.Height; y++)
 71            {
 72                for (int x = 0; x < area.Width; x++)
 73                {
 74                    float rSum = 0, gSum = 0, bSum = 0, aSum = 0;
 75                    float weightSum = 0;
 76
 77                    for (int k = 0; k < kernelSize; k++)
 78                    {
 79                        int nx = x + k - half;
 80                        if (nx >= 0 && nx < area.Width)
 81                        {
 82                            int idx = y * stride + nx * 4;
 83                            float w = kernel[k];
 84                            bSum += pixels[idx] * w;
 85                            gSum += pixels[idx + 1] * w;
 86                            rSum += pixels[idx + 2] * w;
 87                            aSum += pixels[idx + 3] * w;
 88                            weightSum += w;
 89                        }
 90                    }
 91
 92                    int outIdx = y * stride + x * 4;
 93                    temp[outIdx] = (byte)(bSum / weightSum);
 94                    temp[outIdx + 1] = (byte)(gSum / weightSum);
 95                    temp[outIdx + 2] = (byte)(rSum / weightSum);
 96                    temp[outIdx + 3] = (byte)(aSum / weightSum);
 97                }
 98            }
 99
100            // Vertical pass with Gaussian weights
101            for (int y = 0; y < area.Height; y++)
102            {
103                for (int x = 0; x < area.Width; x++)
104                {
105                    float rSum = 0, gSum = 0, bSum = 0, aSum = 0;
106                    float weightSum = 0;
107
108                    for (int k = 0; k < kernelSize; k++)
109                    {
110                        int ny = y + k - half;
111                        if (ny >= 0 && ny < area.Height)
112                        {
113                            int idx = ny * stride + x * 4;
114                            float w = kernel[k];
115                            bSum += temp[idx] * w;
116                            gSum += temp[idx + 1] * w;
117                            rSum += temp[idx + 2] * w;
118                            aSum += temp[idx + 3] * w;
119                            weightSum += w;
120                        }
121                    }
122
123                    int outIdx = y * stride + x * 4;
124                    pixels[outIdx] = (byte)(bSum / weightSum);
125                    pixels[outIdx + 1] = (byte)(gSum / weightSum);
126                    pixels[outIdx + 2] = (byte)(rSum / weightSum);
127                    pixels[outIdx + 3] = (byte)(aSum / weightSum);
128                }
129            }
130
131            System.Runtime.InteropServices.Marshal.Copy(pixels, 0, data.Scan0, bytes);
132            }
133            finally { bmp.UnlockBits(data); }
134        }
135
136        /// <summary>
137        /// Apply blur outside the specified rectangle (inverted blur).
138        /// </summary>
139        public static void ApplyInvertedBlur(Bitmap bmp, Rectangle clearArea, int radius)
140        {
141            ApplyInvertedBlur(bmp, null, clearArea, radius);
142        }
143
144        /// <summary>
145        /// Apply blur to the entire bitmap except a rectangular area.
146        /// If original is provided, restore clearArea from original instead of pre-blur state.
147        /// </summary>
148        public static void ApplyInvertedBlur(Bitmap bmp, Bitmap original, Rectangle clearArea, int radius)
149        {
150            // Create a copy of the clear area from original (or bmp if no original)
151            Bitmap source = original ?? bmp;
152            Bitmap clearPart = new Bitmap(clearArea.Width, clearArea.Height);
153            using (var g = Graphics.FromImage(clearPart))
154            {
155                // Use nearest neighbor to avoid edge artifacts
156                g.InterpolationMode = InterpolationMode.NearestNeighbor;
157                g.PixelOffsetMode = PixelOffsetMode.Half;
158                g.DrawImage(source, 0, 0, clearArea, GraphicsUnit.Pixel);
159            }
160
161            // Blur the entire image
162            ApplyBlur(bmp, new Rectangle(0, 0, bmp.Width, bmp.Height), radius);
163
164            // Restore the clear area from original
165            using (var g = Graphics.FromImage(bmp))
166            {
167                // Use nearest neighbor to avoid edge artifacts
168                g.InterpolationMode = InterpolationMode.NearestNeighbor;
169                g.PixelOffsetMode = PixelOffsetMode.Half;
170                g.CompositingMode = CompositingMode.SourceCopy;
171                g.DrawImage(clearPart, clearArea.X, clearArea.Y);
172            }
173
174            clearPart.Dispose();
175        }
176
177        /// <summary>
178        /// Apply horizontal motion blur to a rectangular area.
179        /// </summary>
180        public static void ApplyMotionBlur(Bitmap bmp, Rectangle area, int distance)
181        {
182            if (distance < 1) return;
183
184            // Clamp area to bitmap bounds
185            area = Rectangle.Intersect(area, new Rectangle(0, 0, bmp.Width, bmp.Height));
186            if (area.Width <= 0 || area.Height <= 0) return;
187
188            var data = bmp.LockBits(area, ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);
189            try
190            {
191            int stride = data.Stride;
192            int bytes = Math.Abs(stride) * area.Height;
193            byte[] pixels = new byte[bytes];
194            byte[] result = new byte[bytes];
195
196            System.Runtime.InteropServices.Marshal.Copy(data.Scan0, pixels, 0, bytes);
197
198            // Horizontal motion blur
199            for (int y = 0; y < area.Height; y++)
200            {
201                for (int x = 0; x < area.Width; x++)
202                {
203                    int rSum = 0, gSum = 0, bSum = 0, aSum = 0;
204                    int count = 0;
205
206                    for (int dx = -distance; dx <= distance; dx++)
207                    {
208                        int nx = x + dx;
209                        if (nx >= 0 && nx < area.Width)
210                        {
211                            int idx = y * stride + nx * 4;
212                            bSum += pixels[idx];
213                            gSum += pixels[idx + 1];
214                            rSum += pixels[idx + 2];
215                            aSum += pixels[idx + 3];
216                            count++;
217                        }
218                    }
219
220                    int outIdx = y * stride + x * 4;
221                    result[outIdx] = (byte)(bSum / count);
222                    result[outIdx + 1] = (byte)(gSum / count);
223                    result[outIdx + 2] = (byte)(rSum / count);
224                    result[outIdx + 3] = (byte)(aSum / count);
225                }
226            }
227
228            System.Runtime.InteropServices.Marshal.Copy(result, 0, data.Scan0, bytes);
229            }
230            finally { bmp.UnlockBits(data); }
231        }
232
233        /// <summary>
234        /// Apply inverted motion blur (blur outside, clear inside).
235        /// </summary>
236        public static void ApplyInvertedMotionBlur(Bitmap bmp, Rectangle clearArea, int distance)
237        {
238            ApplyInvertedMotionBlur(bmp, null, clearArea, distance);
239        }
240
241        /// <summary>
242        /// Apply motion blur outside a rectangular area.
243        /// If original is provided, restore clearArea from original instead of pre-blur state.
244        /// </summary>
245        public static void ApplyInvertedMotionBlur(Bitmap bmp, Bitmap original, Rectangle clearArea, int distance)
246        {
247            // Create a copy of the clear area from original (or bmp if no original)
248            Bitmap source = original ?? bmp;
249            Bitmap clearPart = new Bitmap(clearArea.Width, clearArea.Height);
250            using (var g = Graphics.FromImage(clearPart))
251            {
252                // Use nearest neighbor to avoid edge artifacts
253                g.InterpolationMode = InterpolationMode.NearestNeighbor;
254                g.PixelOffsetMode = PixelOffsetMode.Half;
255                g.DrawImage(source, 0, 0, clearArea, GraphicsUnit.Pixel);
256            }
257
258            // Apply motion blur to entire image
259            ApplyMotionBlur(bmp, new Rectangle(0, 0, bmp.Width, bmp.Height), distance);
260
261            // Restore the clear area from original
262            using (var g = Graphics.FromImage(bmp))
263            {
264                // Use nearest neighbor to avoid edge artifacts
265                g.InterpolationMode = InterpolationMode.NearestNeighbor;
266                g.PixelOffsetMode = PixelOffsetMode.Half;
267                g.CompositingMode = CompositingMode.SourceCopy;
268                g.DrawImage(clearPart, clearArea.X, clearArea.Y);
269            }
270
271            clearPart.Dispose();
272        }
273
274        /// <summary>
275        /// Apply dim (darken) effect to a rectangular area.
276        /// </summary>
277        public static void ApplyDim(Bitmap bmp, Rectangle area, int alpha)
278        {
279            area = Rectangle.Intersect(area, new Rectangle(0, 0, bmp.Width, bmp.Height));
280            if (area.Width <= 0 || area.Height <= 0) return;
281
282            using (var g = Graphics.FromImage(bmp))
283            using (var brush = new SolidBrush(Color.FromArgb(alpha, 0, 0, 0)))
284            {
285                g.FillRectangle(brush, area);
286            }
287        }
288
289        /// <summary>
290        /// Apply feathered (soft) edges to a rectangular area.
291        /// </summary>
292        public static void ApplyFeather(Bitmap bmp, Rectangle area, int featherWidth)
293        {
294            if (featherWidth < 1) return;
295
296            area = Rectangle.Intersect(area, new Rectangle(0, 0, bmp.Width, bmp.Height));
297            if (area.Width <= 0 || area.Height <= 0) return;
298
299            var data = bmp.LockBits(area, ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);
300            int stride = data.Stride;
301            int bytes = Math.Abs(stride) * area.Height;
302            byte[] pixels = new byte[bytes];
303
304            System.Runtime.InteropServices.Marshal.Copy(data.Scan0, pixels, 0, bytes);
305
306            // Apply feather effect - fade alpha near edges
307            for (int y = 0; y < area.Height; y++)
308            {
309                for (int x = 0; x < area.Width; x++)
310                {
311                    // Calculate distance to nearest edge
312                    int distLeft = x;
313                    int distRight = area.Width - 1 - x;
314                    int distTop = y;
315                    int distBottom = area.Height - 1 - y;
316                    int minDist = Math.Min(Math.Min(distLeft, distRight), Math.Min(distTop, distBottom));
317
318                    if (minDist < featherWidth)
319                    {
320                        float factor = (float)minDist / featherWidth;
321                        int idx = y * stride + x * 4;
322                        // Blend with transparent
323                        pixels[idx + 3] = (byte)(pixels[idx + 3] * factor);
324                    }
325                }
326            }
327
328            System.Runtime.InteropServices.Marshal.Copy(pixels, 0, data.Scan0, bytes);
329            bmp.UnlockBits(data);
330        }
331
332        /// <summary>
333        /// Simple box blur implementation.
334        /// </summary>
335        private static void ApplyBoxBlur(Bitmap bmp, Rectangle area, int radius)
336        {
337            if (radius < 1) return;
338
339            var data = bmp.LockBits(area, ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);
340            int stride = data.Stride;
341            int bytes = Math.Abs(stride) * area.Height;
342            byte[] pixels = new byte[bytes];
343            byte[] temp = new byte[bytes];
344
345            System.Runtime.InteropServices.Marshal.Copy(data.Scan0, pixels, 0, bytes);
346
347            // Horizontal pass
348            for (int y = 0; y < area.Height; y++)
349            {
350                for (int x = 0; x < area.Width; x++)
351                {
352                    int rSum = 0, gSum = 0, bSum = 0, aSum = 0;
353                    int count = 0;
354
355                    for (int dx = -radius; dx <= radius; dx++)
356                    {
357                        int nx = x + dx;
358                        if (nx >= 0 && nx < area.Width)
359                        {
360                            int idx = y * stride + nx * 4;
361                            bSum += pixels[idx];
362                            gSum += pixels[idx + 1];
363                            rSum += pixels[idx + 2];
364                            aSum += pixels[idx + 3];
365                            count++;
366                        }
367                    }
368
369                    int outIdx = y * stride + x * 4;
370                    temp[outIdx] = (byte)(bSum / count);
371                    temp[outIdx + 1] = (byte)(gSum / count);
372                    temp[outIdx + 2] = (byte)(rSum / count);
373                    temp[outIdx + 3] = (byte)(aSum / count);
374                }
375            }
376
377            // Vertical pass
378            for (int y = 0; y < area.Height; y++)
379            {
380                for (int x = 0; x < area.Width; x++)
381                {
382                    int rSum = 0, gSum = 0, bSum = 0, aSum = 0;
383                    int count = 0;
384
385                    for (int dy = -radius; dy <= radius; dy++)
386                    {
387                        int ny = y + dy;
388                        if (ny >= 0 && ny < area.Height)
389                        {
390                            int idx = ny * stride + x * 4;
391                            bSum += temp[idx];
392                            gSum += temp[idx + 1];
393                            rSum += temp[idx + 2];
394                            aSum += temp[idx + 3];
395                            count++;
396                        }
397                    }
398
399                    int outIdx = y * stride + x * 4;
400                    pixels[outIdx] = (byte)(bSum / count);
401                    pixels[outIdx + 1] = (byte)(gSum / count);
402                    pixels[outIdx + 2] = (byte)(rSum / count);
403                    pixels[outIdx + 3] = (byte)(aSum / count);
404                }
405            }
406
407            System.Runtime.InteropServices.Marshal.Copy(pixels, 0, data.Scan0, bytes);
408            bmp.UnlockBits(data);
409        }
410
411        /// <summary>
412        /// Create a copy of a rectangular portion of a bitmap.
413        /// </summary>
414        public static Bitmap CopyRegion(Bitmap source, Rectangle area)
415        {
416            area = Rectangle.Intersect(area, new Rectangle(0, 0, source.Width, source.Height));
417            if (area.Width <= 0 || area.Height <= 0) return null;
418
419            var result = new Bitmap(area.Width, area.Height);
420            using (var g = Graphics.FromImage(result))
421            {
422                g.DrawImage(source, 0, 0, area, GraphicsUnit.Pixel);
423            }
424            return result;
425        }
426
427        /// <summary>
428        /// Restore a region from a backup bitmap.
429        /// </summary>
430        public static void RestoreRegion(Bitmap target, Bitmap backup, Rectangle area)
431        {
432            using (var g = Graphics.FromImage(target))
433            {
434                g.DrawImage(backup, area.X, area.Y);
435            }
436        }
437
438        /// <summary>
439        /// Apply fade blur effect - gradient blur around the outer edge of highlight area.
440        /// Creates smooth transition from clear center to blurred edge.
441        /// </summary>
442        public static void ApplyFadeBlur(Bitmap bmp, Bitmap original, Rectangle highlightArea, int fadeWidth, int blurRadius)
443        {
444            if (fadeWidth < 1 || blurRadius < 1) return;
445            if (bmp == null || original == null) return;
446
447            // Create a fully blurred version
448            Bitmap blurred = (Bitmap)original.Clone();
449            ApplyBlur(blurred, new Rectangle(0, 0, blurred.Width, blurred.Height), blurRadius);
450
451            // Now blend: inside highlight = original, outside = blurred, edge = gradient
452            var data = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height),
453                ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);
454            var origData = original.LockBits(new Rectangle(0, 0, original.Width, original.Height),
455                ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
456            var blurData = blurred.LockBits(new Rectangle(0, 0, blurred.Width, blurred.Height),
457                ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
458
459            int stride = data.Stride;
460            int bytes = Math.Abs(stride) * bmp.Height;
461            byte[] pixels = new byte[bytes];
462            byte[] origPixels = new byte[bytes];
463            byte[] blurPixels = new byte[bytes];
464
465            System.Runtime.InteropServices.Marshal.Copy(data.Scan0, pixels, 0, bytes);
466            System.Runtime.InteropServices.Marshal.Copy(origData.Scan0, origPixels, 0, bytes);
467            System.Runtime.InteropServices.Marshal.Copy(blurData.Scan0, blurPixels, 0, bytes);
468
469            // Process all pixels
470            for (int y = 0; y < bmp.Height; y++)
471            {
472                for (int x = 0; x < bmp.Width; x++)
473                {
474                    int idx = y * stride + x * 4;
475
476                    // Calculate distance from highlight border (negative = inside, positive = outside)
477                    int distX = 0, distY = 0;
478
479                    if (x < highlightArea.Left)
480                        distX = highlightArea.Left - x;
481                    else if (x >= highlightArea.Right)
482                        distX = x - highlightArea.Right + 1;
483
484                    if (y < highlightArea.Top)
485                        distY = highlightArea.Top - y;
486                    else if (y >= highlightArea.Bottom)
487                        distY = y - highlightArea.Bottom + 1;
488
489                    // Inside highlight
490                    if (distX == 0 && distY == 0)
491                    {
492                        // Check distance from inner edge
493                        int innerDistX = Math.Min(x - highlightArea.Left, highlightArea.Right - 1 - x);
494                        int innerDistY = Math.Min(y - highlightArea.Top, highlightArea.Bottom - 1 - y);
495                        int innerDist = Math.Min(innerDistX, innerDistY);
496
497                        if (innerDist < fadeWidth)
498                        {
499                            // Gradient from edge to center
500                            float factor = (float)innerDist / fadeWidth;
501                            // 0 at edge = more blur, 1 at center = original
502                            pixels[idx] = (byte)(origPixels[idx] * factor + blurPixels[idx] * (1 - factor));
503                            pixels[idx + 1] = (byte)(origPixels[idx + 1] * factor + blurPixels[idx + 1] * (1 - factor));
504                            pixels[idx + 2] = (byte)(origPixels[idx + 2] * factor + blurPixels[idx + 2] * (1 - factor));
505                            pixels[idx + 3] = (byte)(origPixels[idx + 3] * factor + blurPixels[idx + 3] * (1 - factor));
506                        }
507                        else
508                        {
509                            // Center = original
510                            pixels[idx] = origPixels[idx];
511                            pixels[idx + 1] = origPixels[idx + 1];
512                            pixels[idx + 2] = origPixels[idx + 2];
513                            pixels[idx + 3] = origPixels[idx + 3];
514                        }
515                    }
516                    else
517                    {
518                        // Outside highlight = blurred
519                        pixels[idx] = blurPixels[idx];
520                        pixels[idx + 1] = blurPixels[idx + 1];
521                        pixels[idx + 2] = blurPixels[idx + 2];
522                        pixels[idx + 3] = blurPixels[idx + 3];
523                    }
524                }
525            }
526
527            System.Runtime.InteropServices.Marshal.Copy(pixels, 0, data.Scan0, bytes);
528            bmp.UnlockBits(data);
529            original.UnlockBits(origData);
530            blurred.UnlockBits(blurData);
531            blurred.Dispose();
532        }
533
534        /// <summary>
535        /// Apply feather effect at the border of a clear area.
536        /// Creates smooth gradient transition from blurred to clear at the edges.
537        /// </summary>
538        public static void ApplyBorderFeather(Bitmap bmp, Rectangle clearArea, int featherWidth)
539        {
540            if (featherWidth < 1) return;
541            if (bmp == null) return;
542
543            // Work with the entire bitmap to blend edges
544            var data = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height),
545                ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);
546            int stride = data.Stride;
547            int bytes = Math.Abs(stride) * bmp.Height;
548            byte[] pixels = new byte[bytes];
549
550            System.Runtime.InteropServices.Marshal.Copy(data.Scan0, pixels, 0, bytes);
551
552            // Create a copy for reading original values
553            byte[] original = new byte[bytes];
554            Array.Copy(pixels, original, bytes);
555
556            // Define the feather zone (expanded area around clearArea)
557            int fLeft = Math.Max(0, clearArea.Left - featherWidth);
558            int fTop = Math.Max(0, clearArea.Top - featherWidth);
559            int fRight = Math.Min(bmp.Width, clearArea.Right + featherWidth);
560            int fBottom = Math.Min(bmp.Height, clearArea.Bottom + featherWidth);
561
562            // Process pixels in the feather zone
563            for (int y = fTop; y < fBottom; y++)
564            {
565                for (int x = fLeft; x < fRight; x++)
566                {
567                    // Skip if completely inside clear area
568                    if (x >= clearArea.Left && x < clearArea.Right &&
569                        y >= clearArea.Top && y < clearArea.Bottom)
570                        continue;
571
572                    // Calculate distance to the clear area border
573                    int distX = 0, distY = 0;
574
575                    if (x < clearArea.Left)
576                        distX = clearArea.Left - x;
577                    else if (x >= clearArea.Right)
578                        distX = x - clearArea.Right + 1;
579
580                    if (y < clearArea.Top)
581                        distY = clearArea.Top - y;
582                    else if (y >= clearArea.Bottom)
583                        distY = y - clearArea.Bottom + 1;
584
585                    int dist = (int)Math.Sqrt(distX * distX + distY * distY);
586
587                    if (dist < featherWidth)
588                    {
589                        // Blend factor: 0 at border (fully blurred), 1 at featherWidth (original blur)
590                        float factor = (float)dist / featherWidth;
591                        int idx = y * stride + x * 4;
592
593                        // Blend between clear (original inside clear area) and blurred
594                        // For outside areas, darken/fade the effect
595                        byte newAlpha = (byte)(pixels[idx + 3] * (0.3f + 0.7f * factor));
596                        pixels[idx + 3] = newAlpha;
597                    }
598                }
599            }
600
601            System.Runtime.InteropServices.Marshal.Copy(pixels, 0, data.Scan0, bytes);
602            bmp.UnlockBits(data);
603        }
604    }
605}