Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

[Contribution] Texture2D blur in c#

Discussion in 'Scripting' started by fogman, Jun 10, 2013.

  1. fogman

    fogman

    Joined:
    Jan 21, 2009
    Posts:
    71
    Based on this one from Eric Willis:
    http://notes.ericwillis.com/2009/10/blur-an-image-with-csharp/

    Code (csharp):
    1.  
    2.     private Texture2D Blur(Texture2D image, int blurSize)
    3.     {
    4.         Texture2D blurred = new Texture2D(image.width, image.height);
    5.      
    6.         // look at every pixel in the blur rectangle
    7.         for (int xx = 0; xx < image.width; xx++)
    8.         {
    9.             for (int yy = 0; yy < image.height; yy++)
    10.             {
    11.                 float avgR = 0, avgG = 0, avgB = 0, avgA = 0;
    12.                 int blurPixelCount = 0;
    13.      
    14.                 // average the color of the red, green and blue for each pixel in the
    15.                 // blur size while making sure you don't go outside the image bounds
    16.                 for (int x = xx; (x < xx + blurSize  x < image.width); x++)
    17.                 {
    18.                     for (int y = yy; (y < yy + blurSize  y < image.height); y++)
    19.                     {
    20.                         Color pixel = image.GetPixel(x, y);
    21.      
    22.                         avgR += pixel.r;
    23.                         avgG += pixel.g;
    24.                         avgB += pixel.b;
    25.                         avgA += pixel.a;
    26.      
    27.                         blurPixelCount++;
    28.                     }
    29.                 }
    30.      
    31.                 avgR = avgR / blurPixelCount;
    32.                 avgG = avgG / blurPixelCount;
    33.                 avgB = avgB / blurPixelCount;
    34.                 avgA = avgA / blurPixelCount;
    35.      
    36.                 // now that we know the average for the blur size, set each pixel to that color
    37.                 for (int x = xx; x < xx + blurSize  x < image.width; x++)
    38.                     for (int y = yy; y < yy + blurSize  y < image.height; y++)
    39.                         blurred.SetPixel(x, y, new Color(avgR, avgG, avgB, avgA));
    40.             }
    41.         }
    42.         blurred.Apply();
    43.         return blurred;
    44.     }
    45.  
     
    hhoffren likes this.
  2. Steffan-Poulsen

    Steffan-Poulsen

    Joined:
    Dec 27, 2011
    Posts:
    123
    Thanks for this, it really helped me out!

    It was a bit slow for my liking, but I remembered stumbling across this page a few months ago, and thought I would give some optimisation a try.

    I've separated the blur into two passes, one horisontal and one vertical, as shown on the site. Furthermore I've made it blur in regards to pixels on all sides, and not just in front / on top. Oh, and I have converted your code to javascript.

    The best results i have had, in terms of getting a compromise between speed and quality, is by having a relatively low resolution texture and doing multiple blur iterations (2 iterations on a 128x128 texture), with a radius of 5.

    It's just a couple of hours work so far, and I'm sure it can be further improved upon! Thanks again :)


    Code (csharp):
    1. private var avgR : float = 0;
    2.     private var avgG : float = 0;
    3.     private var avgB : float = 0;
    4.     private var avgA : float = 0;
    5.     private var blurPixelCount = 0;
    6.  
    7.     function FastBlur(image : Texture2D, radius : int, iterations : int) : Texture2D {
    8.         var tex : Texture2D = image;
    9.         for (var i = 0; i < iterations; i++) {
    10.             tex = BlurImage(tex, radius, true);
    11.             tex = BlurImage(tex, radius, false);
    12.         }
    13.         return tex;
    14.     }
    15.  
    16.     function BlurImage(image : Texture2D, blurSize : int, horizontal : boolean) : Texture2D {
    17.         var blurred : Texture2D = new Texture2D(image.width, image.height);
    18.         var _W : int = image.width;
    19.         var _H : int = image.height;
    20.         var xx : int; var yy : int; var x : int; var y : int;
    21.  
    22.         if (horizontal) {
    23.         // Horizontal
    24.         for (yy = 0; yy < _H; yy++) {
    25.             for (xx = 0; xx < _W; xx++) {
    26.                 ResetPixel();
    27.                 //Right side of pixel
    28.                 for (x = xx; (x < xx + blurSize  x < _W); x++) {
    29.                     AddPixel(image.GetPixel(x, yy));
    30.                 }
    31.                 //Left side of pixel
    32.                 for (x = xx; (x > xx - blurSize  x > 0); x--) {
    33.                     AddPixel(image.GetPixel(x, yy));
    34.                 }
    35.                 CalcPixel();
    36.                 for (x = xx; x < xx + blurSize  x < _W; x++) {
    37.                     blurred.SetPixel(x, yy, new Color(avgR, avgG, avgB, 1.0));
    38.                 }
    39.             }
    40.         }
    41.         }
    42.         else {
    43.         // Vertical
    44.         for (xx = 0; xx < _W; xx++) {
    45.             for (yy = 0; yy < _H; yy++) {
    46.                 ResetPixel();
    47.                 //Over pixel
    48.                 for (y = yy; (y < yy + blurSize  y < _H); y++) {
    49.                     AddPixel(image.GetPixel(xx, y));
    50.                 }
    51.                 //Under pixel
    52.                 for (y = yy; (y > yy - blurSize  y > 0); y--) {
    53.                     AddPixel(image.GetPixel(xx, y));
    54.                 }
    55.                 CalcPixel();
    56.                 for (y = yy; y < yy + blurSize  y < _H; y++) {
    57.                     blurred.SetPixel(xx, y, new Color(avgR, avgG, avgB, 1.0));
    58.                 }
    59.             }
    60.         }
    61.         }
    62.  
    63.         blurred.Apply();
    64.         return blurred;
    65.     }
    66.  
    67.     function AddPixel(pixel : Color) {
    68.         avgR += pixel.r;
    69.         avgG += pixel.g;
    70.         avgB += pixel.b;
    71.         blurPixelCount++;
    72.     }
    73.  
    74.     function ResetPixel() {
    75.         avgR = 0.0;
    76.         avgG = 0.0;
    77.         avgB = 0.0;
    78.         blurPixelCount = 0;
    79.     }
    80.  
    81.     function CalcPixel() {
    82.         avgR = avgR / blurPixelCount;
    83.         avgG = avgG / blurPixelCount;
    84.         avgB = avgB / blurPixelCount;
    85.     }
     
  3. Elecman

    Elecman

    Joined:
    May 5, 2011
    Posts:
    1,369
    I tested Fogman's code but it shifts a solid circle as an input texture to the lower left corner. The bigger the blurSize, the bigger the shift.

    The code Steffan Poulsen provides doesn't have the shift bug and it is faster to boot. I converted it back into C# as well :cool:

    Code (csharp):
    1.  
    2.  
    3. private float avgR = 0;
    4. private float avgG = 0;
    5. private float avgB = 0;
    6. private float avgA = 0;
    7. private float blurPixelCount = 0;
    8.  
    9. Texture2D FastBlur(Texture2D image, int radius, int iterations){
    10.  
    11.     Texture2D tex = image;
    12.  
    13.     for (var i = 0; i < iterations; i++) {
    14.  
    15.         tex = BlurImage(tex, radius, true);
    16.         tex = BlurImage(tex, radius, false);
    17.     }
    18.  
    19.     return tex;
    20. }
    21.  
    22. Texture2D BlurImage(Texture2D image, int blurSize, bool horizontal){
    23.  
    24.     Texture2D blurred = new Texture2D(image.width, image.height);
    25.     int _W = image.width;
    26.     int _H = image.height;
    27.     int xx, yy, x, y;
    28.  
    29.     if (horizontal) {
    30.  
    31.         for (yy = 0; yy < _H; yy++) {
    32.  
    33.             for (xx = 0; xx < _W; xx++) {
    34.  
    35.                 ResetPixel();
    36.  
    37.                 //Right side of pixel
    38.                 for (x = xx; (x < xx + blurSize  x < _W); x++) {
    39.  
    40.                     AddPixel(image.GetPixel(x, yy));
    41.                 }
    42.  
    43.                 //Left side of pixel
    44.                 for (x = xx; (x > xx - blurSize  x > 0); x--) {
    45.  
    46.                     AddPixel(image.GetPixel(x, yy));
    47.                 }
    48.  
    49.                 CalcPixel();
    50.  
    51.                 for (x = xx; x < xx + blurSize  x < _W; x++) {
    52.  
    53.                     blurred.SetPixel(x, yy, new Color(avgR, avgG, avgB, 1.0f));
    54.                 }
    55.             }
    56.         }
    57.     }
    58.  
    59.     else {
    60.  
    61.         for (xx = 0; xx < _W; xx++) {
    62.  
    63.             for (yy = 0; yy < _H; yy++) {
    64.  
    65.                 ResetPixel();
    66.  
    67.                 //Over pixel
    68.                 for (y = yy; (y < yy + blurSize  y < _H); y++) {
    69.  
    70.                     AddPixel(image.GetPixel(xx, y));
    71.                 }
    72.  
    73.                 //Under pixel
    74.                 for (y = yy; (y > yy - blurSize  y > 0); y--) {
    75.  
    76.                     AddPixel(image.GetPixel(xx, y));
    77.                 }
    78.  
    79.                 CalcPixel();
    80.  
    81.                 for (y = yy; y < yy + blurSize  y < _H; y++) {
    82.  
    83.                     blurred.SetPixel(xx, y, new Color(avgR, avgG, avgB, 1.0f));
    84.                 }
    85.             }
    86.         }
    87.     }
    88.  
    89.     blurred.Apply();
    90.     return blurred;
    91. }
    92.  
    93. void AddPixel(Color pixel) {
    94.  
    95.     avgR += pixel.r;
    96.     avgG += pixel.g;
    97.     avgB += pixel.b;
    98.     blurPixelCount++;
    99. }
    100.  
    101. void ResetPixel() {
    102.  
    103.     avgR = 0.0f;
    104.     avgG = 0.0f;
    105.     avgB = 0.0f;
    106.     blurPixelCount = 0;
    107. }
    108.  
    109. void CalcPixel() {
    110.  
    111.     avgR = avgR / blurPixelCount;
    112.     avgG = avgG / blurPixelCount;
    113.     avgB = avgB / blurPixelCount;
    114. }
    115.  
     
  4. chelnok

    chelnok

    Joined:
    Jul 2, 2012
    Posts:
    680
    Small modification for @Elecman 's version:

    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. public class Blur : MonoBehaviour {
    5.    
    6.     private float avgR = 0;
    7.     private float avgG = 0;
    8.     private float avgB = 0;
    9.     private float avgA = 0;
    10.     private float blurPixelCount = 0;
    11.    
    12.     public int radius =2;
    13.     public int iterations =2;
    14.    
    15.     private Texture2D tex;
    16.    
    17.     // Use this for initialization
    18.     void Start () {
    19.         tex = renderer.material.mainTexture as Texture2D;
    20.        
    21.     }
    22.    
    23.     // Update is called once per frame
    24.     void Update () {
    25.        
    26.         if (Input.GetKeyDown(KeyCode.Space))
    27.             renderer.material.mainTexture = FastBlur( tex, radius, iterations);
    28.     }
    29.    
    30.    
    31.     Texture2D FastBlur(Texture2D image, int radius, int iterations){
    32.         Texture2D tex = image;
    33.        
    34.         for (var i = 0; i < iterations; i++) {
    35.            
    36.             tex = BlurImage(tex, radius, true);
    37.             tex = BlurImage(tex, radius, false);
    38.            
    39.         }
    40.        
    41.         return tex;
    42.     }
    43.    
    44.    
    45.    
    46.     Texture2D BlurImage(Texture2D image, int blurSize, bool horizontal){
    47.        
    48.         Texture2D blurred = new Texture2D(image.width, image.height);
    49.         int _W = image.width;
    50.         int _H = image.height;
    51.         int xx, yy, x, y;
    52.        
    53.         if (horizontal) {
    54.             for (yy = 0; yy < _H; yy++) {
    55.                 for (xx = 0; xx < _W; xx++) {
    56.                     ResetPixel();
    57.                    
    58.                     //Right side of pixel
    59.                    
    60.                     for (x = xx; (x < xx + blurSize && x < _W); x++) {
    61.                         AddPixel(image.GetPixel(x, yy));
    62.                     }
    63.                    
    64.                     //Left side of pixel
    65.                    
    66.                     for (x = xx; (x > xx - blurSize && x > 0); x--) {
    67.                         AddPixel(image.GetPixel(x, yy));
    68.                        
    69.                     }
    70.                    
    71.                    
    72.                     CalcPixel();
    73.                    
    74.                     for (x = xx; x < xx + blurSize && x < _W; x++) {
    75.                         blurred.SetPixel(x, yy, new Color(avgR, avgG, avgB, 1.0f));
    76.                        
    77.                     }
    78.                 }
    79.             }
    80.         }
    81.        
    82.         else {
    83.             for (xx = 0; xx < _W; xx++) {
    84.                 for (yy = 0; yy < _H; yy++) {
    85.                     ResetPixel();
    86.                    
    87.                     //Over pixel
    88.                    
    89.                     for (y = yy; (y < yy + blurSize && y < _H); y++) {
    90.                         AddPixel(image.GetPixel(xx, y));
    91.                     }
    92.                     //Under pixel
    93.                    
    94.                     for (y = yy; (y > yy - blurSize && y > 0); y--) {
    95.                         AddPixel(image.GetPixel(xx, y));
    96.                     }
    97.                     CalcPixel();
    98.                     for (y = yy; y < yy + blurSize && y < _H; y++) {
    99.                         blurred.SetPixel(xx, y, new Color(avgR, avgG, avgB, 1.0f));
    100.                        
    101.                     }
    102.                 }
    103.             }
    104.         }
    105.        
    106.         blurred.Apply();
    107.         return blurred;
    108.     }
    109.     void AddPixel(Color pixel) {
    110.         avgR += pixel.r;
    111.         avgG += pixel.g;
    112.         avgB += pixel.b;
    113.         blurPixelCount++;
    114.     }
    115.    
    116.     void ResetPixel() {
    117.         avgR = 0.0f;
    118.         avgG = 0.0f;
    119.         avgB = 0.0f;
    120.         blurPixelCount = 0;
    121.     }
    122.    
    123.     void CalcPixel() {
    124.         avgR = avgR / blurPixelCount;
    125.         avgG = avgG / blurPixelCount;
    126.         avgB = avgB / blurPixelCount;
    127.     }
    128. }
    Add script and texture to quad or plane for example. Texture needs to be marked as read / write. Hit play and press space to blur.
     
    Last edited: Nov 14, 2014
    hhoffren and Nukode like this.
  5. cherub

    cherub

    Joined:
    Apr 26, 2006
    Posts:
    493
    am i doing something wrong? all of these script fail at the first use of the variable x.
     
  6. chelnok

    chelnok

    Joined:
    Jul 2, 2012
    Posts:
    680
    Looks like new forum software has erased && symbols (double ampersand) from code. I edited my post, but if you wish to use other versions, you need to fix all this kind of parts from the code:

    Code (CSharp):
    1.  //Left side of pixel
    2.                 for (x = xx; (x > xx - blurSize  x > 0); x--) {
    TO:

    Code (CSharp):
    1.  //Left side of pixel
    2.                 for (x = xx; (x > xx - blurSize && x > 0); x--) {
     
  7. Cardinalby

    Cardinalby

    Joined:
    Oct 9, 2014
    Posts:
    1
    My optimized version

    Code (CSharp):
    1. class LinearBlur
    2.     {
    3.         private float _rSum = 0;
    4.         private float _gSum = 0;
    5.         private float _bSum = 0;
    6.  
    7.         private Texture2D _sourceImage;
    8.         private int _sourceWidth;
    9.         private int _sourceHeight;
    10.         private int _windowSize;
    11.  
    12.         public Texture2D Blur(Texture2D image, int radius, int iterations)
    13.         {
    14.             _windowSize = radius * 2 + 1;
    15.             _sourceWidth = image.width;
    16.             _sourceHeight = image.height;
    17.  
    18.             var tex = image;
    19.  
    20.             for (var i = 0; i < iterations; i++)
    21.             {
    22.                 tex = OneDimensialBlur(tex, radius, true);
    23.                 tex = OneDimensialBlur(tex, radius, false);
    24.             }
    25.  
    26.             return tex;
    27.         }
    28.  
    29.         private Texture2D OneDimensialBlur(Texture2D image, int radius, bool horizontal)
    30.         {
    31.             _sourceImage = image;
    32.  
    33.             var blurred = new Texture2D(image.width, image.height, image.format, false);
    34.  
    35.             if (horizontal)
    36.             {
    37.                 for (int imgY = 0; imgY < _sourceHeight; ++imgY)
    38.                 {
    39.                     ResetSum();
    40.  
    41.                     for (int imgX = 0; imgX < _sourceWidth; imgX++)
    42.                     {
    43.                         if (imgX == 0)
    44.                             for (int x = radius * -1; x <= radius; ++x)
    45.                                 AddPixel(GetPixelWithXCheck(x, imgY));
    46.                         else
    47.                         {
    48.                             var toExclude = GetPixelWithXCheck(imgX - radius - 1, imgY);
    49.                             var toInclude = GetPixelWithXCheck(imgX + radius, imgY);
    50.  
    51.                             SubstPixel(toExclude);
    52.                             AddPixel(toInclude);
    53.                         }
    54.  
    55.                         blurred.SetPixel(imgX, imgY, CalcPixelFromSum());
    56.                     }
    57.                 }
    58.             }
    59.  
    60.             else
    61.             {
    62.                 for (int imgX = 0; imgX < _sourceWidth; imgX++)
    63.                 {
    64.                     ResetSum();
    65.  
    66.                     for (int imgY = 0; imgY < _sourceHeight; ++imgY)
    67.                     {
    68.                         if (imgY == 0)
    69.                             for (int y = radius * -1; y <= radius; ++y)
    70.                                 AddPixel(GetPixelWithYCheck(imgX, y));
    71.                         else
    72.                         {
    73.                             var toExclude = GetPixelWithYCheck(imgX, imgY - radius - 1);
    74.                             var toInclude = GetPixelWithYCheck(imgX, imgY + radius);
    75.  
    76.                             SubstPixel(toExclude);
    77.                             AddPixel(toInclude);
    78.                         }
    79.  
    80.                         blurred.SetPixel(imgX, imgY, CalcPixelFromSum());
    81.                     }
    82.                 }
    83.             }
    84.  
    85.             blurred.Apply();
    86.             return blurred;
    87.         }
    88.  
    89.         private Color GetPixelWithXCheck(int x, int y)
    90.         {
    91.             if (x <= 0) return _sourceImage.GetPixel(0, y);
    92.             if (x >= _sourceWidth) return _sourceImage.GetPixel(_sourceWidth - 1, y);
    93.             return _sourceImage.GetPixel(x, y);
    94.         }
    95.  
    96.         private Color GetPixelWithYCheck(int x, int y)
    97.         {
    98.             if (y <= 0) return _sourceImage.GetPixel(x, 0);
    99.             if (y >= _sourceHeight) return _sourceImage.GetPixel(x, _sourceHeight - 1);
    100.             return _sourceImage.GetPixel(x, y);
    101.         }
    102.  
    103.         private void AddPixel(Color pixel)
    104.         {
    105.             _rSum += pixel.r;
    106.             _gSum += pixel.g;
    107.             _bSum += pixel.b;
    108.         }
    109.  
    110.         private void SubstPixel(Color pixel)
    111.         {
    112.             _rSum -= pixel.r;
    113.             _gSum -= pixel.g;
    114.             _bSum -= pixel.b;
    115.         }
    116.  
    117.         private void ResetSum()
    118.         {
    119.             _rSum = 0.0f;
    120.             _gSum = 0.0f;
    121.             _bSum = 0.0f;
    122.         }
    123.  
    124.         Color CalcPixelFromSum()
    125.         {
    126.             return new Color(_rSum / _windowSize, _gSum / _windowSize, _bSum / _windowSize);
    127.         }
    128.     }
     
    hhoffren and DBarlok like this.
  8. Hiti3

    Hiti3

    Joined:
    Jul 16, 2015
    Posts:
    11
    Is it any better/ more optimized than Elecman's code?
     
  9. Hiti3

    Hiti3

    Joined:
    Jul 16, 2015
    Posts:
    11
    Could someone please please explain how to test this? I have an image and I want to test it out in my scene, however when I want to set the mainTexture of the image it says its Read only. Im googling hours and hours, sorry I'm new to Unity, but I would really need your help.
     
  10. Hiti3

    Hiti3

    Joined:
    Jul 16, 2015
    Posts:
    11
    Could you tell me how did you magate to test this? Im using Raw image but I dont know how much to set for radius to have the whole image blurred... And nothing is vissible :(
     
  11. DayFall

    DayFall

    Joined:
    Jan 2, 2015
    Posts:
    1
    My small benchmark test shown that "LinearBlur" by @Cardinalby much FASTER than @Elecman 's blur (about 8 to 10 times faster!). May be it does not so nice blur, but speed was more important for me.
    However, posted code isn`t fully optimized and has a memory leak.
    Code (CSharp):
    1. var blurred = new Texture2D(image.width, image.height, image.format, false);
    Creating new Texture2D in loop is not a good idea. GC won`t get them, even if it`s a local variable. Memory allocates every time and you can free it by calling DestroyImmediate(blurred); (this is the only way AFAIK).
    So I`d suggest to make this class static and use 2 static textures for all purposes.
    Also we can avoid calling this every pass:
    Code (CSharp):
    1. blurred.Apply();
    and call it one time in the end.
     
  12. andrew-lukasik

    andrew-lukasik

    Joined:
    Jan 31, 2013
    Posts:
    249
    IJob + BurstCompiled

    Note: you can quite easily improve this job so it won't perform any conversions by getting rid of these pedestrian Color[] inputs/outputs completely and replacing them with just an input NativeArray of texture.GetRawTextureData<this texture-specific type>

    Code (CSharp):
    1. using Unity.Mathematics;
    2. using Unity.Collections;
    3.  
    4. [Unity.Burst.BurstCompile]
    5. struct LinearBlurJob : Unity.Jobs.IJob
    6. {
    7.     NativeArray<float4> source, blurred;//float4 is simd, Color isn't, hence in/out conversions (trading memory for speed here)
    8.     int width, height, boxSize, iterations, radius;
    9.     public ColorArrayJob ( UnityEngine.Color[] source , int width , int height , int radius , int iterations )
    10.     {
    11.         this.source = new NativeArray<float4>( source.Length , Allocator.TempJob , NativeArrayOptions.UninitializedMemory );
    12.         for( int i=source.Length-1 ; i!=-1 ; i-- )
    13.         {
    14.             var col = source[ i ];
    15.             this.source[ i ] = new float4{ x=col.r , y=col.g , z=col.b , w=col.a };
    16.         }
    17.         this.blurred = new NativeArray<float4>( this.source , Allocator.TempJob );
    18.         this.radius = radius;
    19.         this.boxSize = radius * 2 + 1;
    20.         this.width = width;
    21.         this.height = height;
    22.         this.iterations = iterations;
    23.     }
    24.     void Unity.Jobs.IJob.Execute ()
    25.     {
    26.         for( int iteration=0 ; iteration<iterations ; iteration++ )
    27.         {
    28.             // horizontal blur:
    29.             blurred.CopyTo( source );
    30.             for( int Y=0 ; Y<height ; ++Y )
    31.             {
    32.                 float4 sum = default(float4);
    33.                 {
    34.                     for( int x=-radius ; x<=radius ; ++x ) sum += GetValueSafe( source , x , Y );
    35.                     blurred[ To1dIndex(0,Y) ] = sum/boxSize;
    36.                 }
    37.                 for( int X=1 ; X<width ; X++ )
    38.                 {
    39.                     sum -= GetValueSafe( source , X-radius-1 , Y );
    40.                     sum += GetValueSafe( source , X + radius , Y );
    41.                     blurred[ To1dIndex(X,Y) ] = sum/boxSize;
    42.                 }
    43.             }
    44.             // vertical blur:
    45.             blurred.CopyTo( source );
    46.             for( int X=0 ; X<width ; X++ )
    47.             {
    48.                 float4 sum = default(float4);
    49.                 {
    50.                     for( int y=-radius ; y<=radius ; ++y ) sum += GetValueSafe( source , X , y );
    51.                     blurred[ To1dIndex(X,0) ] = sum/boxSize;
    52.                 }
    53.                 for( int Y=1 ; Y<height ; ++Y )
    54.                 {
    55.                     sum -= GetValueSafe( source , X , Y-radius-1 );
    56.                     sum += GetValueSafe( source , X , Y+radius );
    57.                     blurred[ To1dIndex(X,Y) ] = sum/boxSize;
    58.                 }
    59.             }
    60.         }
    61.     }
    62.     float4 GetValueSafe ( NativeArray<float4> array , int x , int y ) => array[ To1dIndex( math.clamp( x , 0 , width-1 ) , math.clamp( y , 0 , height-1 ) ) ];
    63.     int To1dIndex ( int x , int y ) => y * width + x;
    64.     public UnityEngine.Color[] GetResultsAndClose ()
    65.     {
    66.         UnityEngine.Color[] results = new UnityEngine.Color[ blurred.Length ];
    67.         for( int i=blurred.Length-1 ; i!=-1 ; i-- )
    68.         {
    69.             var f4 = blurred[ i ];
    70.             results[ i ] = new UnityEngine.Color{ r=f4.x , g=f4.y , b=f4.z , a=f4.w };
    71.         }
    72.         source.Dispose();
    73.         blurred.Dispose();
    74.         return results;
    75.     }
    76. }
    77.  
     
    Last edited: Apr 4, 2019
    jhocking-bundlar likes this.
  13. andrew-lukasik

    andrew-lukasik

    Joined:
    Jan 31, 2013
    Posts:
    249
    Here is an example of a mentioned improved IJob that blurs texture.GetRawTextureData<UInt16> (aka ushort) where texture format is R16 (16bit grayscale):
    NativeUInt16Job
     
    Bunny83 likes this.
  14. nbg_yalta

    nbg_yalta

    Joined:
    Oct 3, 2012
    Posts:
    378
    How do I use it to blur my 2D Texture?
     
  15. andrew-lukasik

    andrew-lukasik

    Joined:
    Jan 31, 2013
    Posts:
    249
    LinearBlur.ColorArrayJob is definitely a simplest option if you're looking for that:
    Code (CSharp):
    1. var blurJob = new LinearBlur.ColorArrayJob(
    2.     texture.GetPixels() ,
    3.     texture.width ,
    4.     texture.height ,
    5.     Mathf.Min( texture.width , texture.height )/200 ,
    6.     5
    7. );
    8. blurJob.Schedule().Complete();
    9. texture.SetPixels( blurJob.GetResultsAndClose() );
    10. texture.Apply();
    It allocates Color[] but it gets job done without much thinking about what is what.

    Every other option requires you to think about data structures and matching those 1:1 precisely. For example, LinearBlur.NativeUInt16Job is dedicated for TextureFormat.R16 only and wont work with anything else. I created my auxiliary doc page so you can familiarize yourself with data received from texture.GetRawTextureData<type> there: RawTextureDataProcessingExamples (start here)

    EDIT: missing line added (@nbg_yalta)
     
    Last edited: Apr 27, 2019
    hippocoder likes this.
  16. nbg_yalta

    nbg_yalta

    Joined:
    Oct 3, 2012
    Posts:
    378
    Still not get it.. my texture is RGB24 format, I tryied to use script from #12 post, but there is an error with ColorArrayJob method doesnt return anything... Also you have mentioned about improvement, but I have no idea how to do that..
     
  17. andrew-lukasik

    andrew-lukasik

    Joined:
    Jan 31, 2013
    Posts:
    249
    (one line was missing, fixed, try now)

    RGB24 struct looks like this:
    Code (CSharp):
    1. struct RGB24 { public byte r, g, b; }
    Simple as that. Naming etc. for these structures doesn't matter. It's memory layout what counts (byte order/map) and what you do with them after that.
     
    Last edited: Apr 27, 2019
    Bunny83 likes this.
  18. Pranjalraut

    Pranjalraut

    Joined:
    Apr 27, 2019
    Posts:
    4
    Excellent thread! It was helpful for me. Keep up!
     
  19. nbg_yalta

    nbg_yalta

    Joined:
    Oct 3, 2012
    Posts:
    378
    Works great, thanks
     
  20. nbg_yalta

    nbg_yalta

    Joined:
    Oct 3, 2012
    Posts:
    378
    Works but... keeps throwing warnings:
    Internal: JobTempAlloc has allocations that are more than 4 frames old - this is not allowed and likely a leak
    and errors
    A Native Collection has not been disposed, resulting in a memory leak. Enable Full StackTraces to get more details.
     
  21. andrew-lukasik

    andrew-lukasik

    Joined:
    Jan 31, 2013
    Posts:
    249
    If you called GetResultsAndClose() then this should disappear after you restart your editor (this is because old leaks will persist and will continue to freak out leak detectors)
     
    Last edited: Apr 27, 2019
  22. andrew-lukasik

    andrew-lukasik

    Joined:
    Jan 31, 2013
    Posts:
    249
    UPDATE: LinearBlur now supports RGB24 and RGBA32 out of the box

    This is how to use it:
    Code (CSharp):
    1. void BlurRGB24 ( Texture2D texture )
    2. {
    3.     if( texture.isReadable==false ) throw new UnityException( "This texture is not accessible from CPU" );
    4.     if( texture.format!=TextureFormat.RGB24 ) throw new UnityException($"Wrong format. Texture is not { TextureFormat.RGB24 } but { texture.format }");
    5.     var job = new LinearBlur.NativeRGB24Job(
    6.         texture.GetRawTextureData<LinearBlur.RGB24>() ,
    7.         texture.width ,
    8.         texture.height ,
    9.         Mathf.Min( texture.width , texture.height )/200 ,
    10.         5
    11.     );
    12.     job.Schedule().Complete();
    13.     job.Close();
    14.     texture.Apply();
    15. }
    Code (CSharp):
    1. void BlurRGBA32 ( Texture2D texture )
    2. {
    3.     if( texture.isReadable==false ) throw new UnityException( "This texture is not accessible from CPU" );
    4.     if( texture.format!=TextureFormat.RGBA32 ) throw new UnityException($"Wrong format. Texture is not { TextureFormat.RGBA32 } but { texture.format }");
    5.     var job = new LinearBlur.NativeRGBA32Job(
    6.         texture.GetRawTextureData<LinearBlur.RGBA32>() ,
    7.         texture.width ,
    8.         texture.height ,
    9.         Mathf.Min( texture.width , texture.height )/200 ,
    10.         5
    11.     );
    12.     job.Schedule().Complete();
    13.     job.Close();
    14.     texture.Apply();
    15. }
     
    Last edited: Jan 5, 2020
  23. andrew-lukasik

    andrew-lukasik

    Joined:
    Jan 31, 2013
    Posts:
    249
    UPDATE: For anyone interested, my RawTextureDataProcessingExamples now includes samples for how to:
    • Invert color (key point: unusual texture formats & their data structures demystified)
    • Edge detection
    • Blur
    • Grayscale
    All examples are implementing IJobParallelFor and utilizing Burst so are faster than previous IJob ones.
    Includes simple tester window (UIElements):

     
    Bunny83 likes this.
  24. Quatum1000

    Quatum1000

    Joined:
    Oct 5, 2014
    Posts:
    889
    Thanks for the tool.

    It good for lite blur on textures.
    But if you like to have more intensive blur then it have some downers.

    Untitled-1.jpg
     
  25. Quatum1000

    Quatum1000

    Joined:
    Oct 5, 2014
    Posts:
    889
    Today I enhanced ParallelGaussianBlur script for Unity

    * Add the script to any GO.
    * Drag and drop a texture to the inspector slot Tex2D.
    * Make sure the texture is writable in inspector.
    * To activate blur press the small checkbox to enable and disable the script.
    * 512x512 takes about ~81 ms to blur.
    * Currently only for non compressed textures. Change texture to RGBA32.

    a.jpg

    ylg8vnfj.gif

    Code (CSharp):
    1. using System.Collections.Generic;
    2. using System.Threading.Tasks;
    3. using Unity.Collections;
    4. using UnityEngine;
    5.  
    6. [ExecuteInEditMode]
    7. public class ParallelGaussianBlur : MonoBehaviour {
    8.  
    9.     // The MIT License
    10.     // Copyright © 2020 Roger Cabo Ashauer
    11.     // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute,     // sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:     // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.     // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,     // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,     // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
    12.     // https://de.wikipedia.org/wiki/MIT-Lizenz
    13.  
    14.     // This solution is based on Fast image convolutions by Wojciech Jarosz.
    15.     // http://elynxsdk.free.fr/ext-docs/Blur/Fast_box_blur.pdf
    16.     // And Ivan Kutskir
    17.     // http://blog.ivank.net/fastest-gaussian-blur.html
    18.     // And Mike Demyl
    19.     // https://github.com/mdymel  // https://github.com/mdymel/superfastblur
    20.  
    21.     public Texture2D Tex2D;
    22.     public int Radial = 1;
    23.     public int _Width;
    24.     public int _Height;
    25.     public int _PixelCount;
    26.     public double TimeUsedMilliseconds = 0;
    27.  
    28.     private int[] m_red;
    29.     private  int[] m_green;
    30.     private  int[] m_blue;
    31.     private  int[] m_alpha;
    32.  
    33.     private readonly ParallelOptions _pOptions = new ParallelOptions {
    34.         MaxDegreeOfParallelism = 8
    35.     };
    36.  
    37.     private NativeArray<Color32> _RawTextureData = new NativeArray<Color32>();
    38.  
    39.     private void OnEnable()
    40.     {
    41.         if (Tex2D != default)
    42.             GaussianBlur(ref Tex2D);
    43.     }
    44.  
    45.     public void GaussianBlur(ref Texture2D tex2D)
    46.     {
    47.         var t = Time.realtimeSinceStartup;
    48.  
    49.         _RawTextureData = tex2D.GetRawTextureData<Color32>();
    50.         _Width = tex2D.width;
    51.         _Height = tex2D.height;
    52.         _PixelCount = tex2D.width * tex2D.height;
    53.  
    54.         _Width = tex2D.width;
    55.         _Height = tex2D.height;
    56.  
    57.         m_red = new int[_Width * _Height];
    58.         m_green = new int[_Width * _Height];
    59.         m_blue = new int[_Width * _Height];
    60.         m_alpha = new int[_Width * _Height];
    61.  
    62.         Parallel.For(0, _Width * _Height, _pOptions, i =>
    63.         {
    64.             m_red[i] = _RawTextureData[i].r;
    65.             m_green[i] = _RawTextureData[i].g;
    66.             m_blue[i] = _RawTextureData[i].b;
    67.             m_alpha[i] = _RawTextureData[i].a;
    68.         });
    69.  
    70.         var newAlpha = new int[_Width * _Height];
    71.         var newRed = new int[_Width * _Height];
    72.         var newGreen = new int[_Width * _Height];
    73.         var newBlue = new int[_Width * _Height];
    74.  
    75.         Parallel.Invoke(
    76.             () => gaussBlur_4(m_alpha, newAlpha, Radial),
    77.             () => gaussBlur_4(m_red, newRed, Radial),
    78.             () => gaussBlur_4(m_green, newGreen, Radial),
    79.             () => gaussBlur_4(m_blue, newBlue, Radial));
    80.  
    81.         Parallel.For(0, _Width * _Height, _pOptions, i =>
    82.         {
    83.             // int[i] = (int)((uint)(newRed[i] << 24) | (uint)(newGreen[i] << 16) | (uint)(newBlue[i] << 8) | (uint)newAlpha[i]);
    84.  
    85.             if (newAlpha[i] > 255) newAlpha[i] = 255;
    86.             if (newRed[i] > 255) newRed[i] = 255;
    87.             if (newGreen[i] > 255) newGreen[i] = 255;
    88.             if (newBlue[i] > 255) newBlue[i] = 255;
    89.  
    90.             if (newAlpha[i] < 0) newAlpha[i] = 0;
    91.             if (newRed[i] < 0) newRed[i] = 0;
    92.             if (newGreen[i] < 0) newGreen[i] = 0;
    93.             if (newBlue[i] < 0) newBlue[i] = 0;
    94.  
    95.             _RawTextureData[i] = new Color32((byte)newRed[i], (byte)newGreen[i], (byte)newBlue[i], (byte)newAlpha[i]);
    96.         });
    97.  
    98.         tex2D.Apply();
    99.         TimeUsedMilliseconds = (Time.realtimeSinceStartup - t) * 1000;
    100.     }
    101.  
    102.     private int[] boxesForGauss(int sigma, int n)
    103.     {
    104.         double wIdeal = System.Math.Sqrt((12 * sigma * sigma / n) + 1);
    105.         int wl = (int)System.Math.Floor(wIdeal);
    106.         if (wl % 2 == 0) wl--;
    107.         int wu = wl + 2;
    108.  
    109.         double mIdeal = (double)(12 * sigma * sigma - n * wl * wl - 4 * n * wl - 3 * n) / (-4 * wl - 4);
    110.         double m = System.Math.Round(mIdeal);
    111.  
    112.         var sizes = new List<int>();
    113.         for (var i = 0; i < n; i++) sizes.Add(i < m ? wl : wu);
    114.         return sizes.ToArray();
    115.     }
    116.  
    117.     private void gaussBlur_4(int[] colorChannel, int[] destChannel, int r)
    118.     {
    119.         int[] bxs = boxesForGauss(r, 3);
    120.         boxBlur_4(colorChannel, destChannel, _Width, _Height, (bxs[0] - 1) / 2);
    121.         boxBlur_4(destChannel, colorChannel, _Width, _Height, (bxs[1] - 1) / 2);
    122.         boxBlur_4(colorChannel, destChannel, _Width, _Height, (bxs[2] - 1) / 2);
    123.     }
    124.  
    125.     private void boxBlur_4(int[] colorChannel, int[] destChannel, int w, int h, int r)
    126.     {
    127.         for (var i = 0; i < colorChannel.Length; i++) destChannel[i] = colorChannel[i];
    128.         boxBlurH_4(destChannel, colorChannel, w, h, r);
    129.         boxBlurT_4(colorChannel, destChannel, w, h, r);
    130.     }
    131.  
    132.     private void boxBlurH_4(int[] colorChannel, int[] dest, int w, int h, int radial)
    133.     {
    134.         var iar = (double)1 / (radial + radial + 1);
    135.         Parallel.For(0, h, _pOptions, i =>
    136.         {
    137.             var ti = i * w;
    138.             var li = ti;
    139.             var ri = ti + radial;
    140.             var fv = colorChannel[ti];
    141.             var lv = colorChannel[ti + w - 1];
    142.             var val = (radial + 1) * fv;
    143.             for (var j = 0; j < radial; j++) val += colorChannel[ti + j];
    144.             for (var j = 0; j <= radial; j++)
    145.             {
    146.                 val += colorChannel[ri++] - fv;
    147.                 dest[ti++] = (int)System.Math.Round(val * iar);
    148.             }
    149.             for (var j = radial + 1; j < w - radial; j++)
    150.             {
    151.                 val += colorChannel[ri++] - dest[li++];
    152.                 dest[ti++] = (int)System.Math.Round(val * iar);
    153.             }
    154.             for (var j = w - radial; j < w; j++)
    155.             {
    156.                 val += lv - colorChannel[li++];
    157.                 dest[ti++] = (int)System.Math.Round(val * iar);
    158.             }
    159.         });
    160.     }
    161.  
    162.     private void boxBlurT_4(int[] colorChannel, int[] dest, int w, int h, int r)
    163.     {
    164.         var iar = (double)1 / (r + r + 1);
    165.         Parallel.For(0, w, _pOptions, i =>
    166.         {
    167.             var ti = i;
    168.             var li = ti;
    169.             var ri = ti + r * w;
    170.             var fv = colorChannel[ti];
    171.             var lv = colorChannel[ti + w * (h - 1)];
    172.             var val = (r + 1) * fv;
    173.             for (var j = 0; j < r; j++) val += colorChannel[ti + j * w];
    174.             for (var j = 0; j <= r; j++)
    175.             {
    176.                 val += colorChannel[ri] - fv;
    177.                 dest[ti] = (int)System.Math.Round(val * iar);
    178.                 ri += w;
    179.                 ti += w;
    180.             }
    181.             for (var j = r + 1; j < h - r; j++)
    182.             {
    183.                 val += colorChannel[ri] - colorChannel[li];
    184.                 dest[ti] = (int)System.Math.Round(val * iar);
    185.                 li += w;
    186.                 ri += w;
    187.                 ti += w;
    188.             }
    189.             for (var j = h - r; j < h; j++)
    190.             {
    191.                 val += lv - colorChannel[li];
    192.                 dest[ti] = (int)System.Math.Round(val * iar);
    193.                 li += w;
    194.                 ti += w;
    195.             }
    196.         });
    197.     }
    198. }
     
  26. andrew-lukasik

    andrew-lukasik

    Joined:
    Jan 31, 2013
    Posts:
    249
    Thank you for bringing this up to my attention @Quatum1000 ! I just added implementations for much better box and gaussian blurs as well.

     
    Last edited: Mar 24, 2020
    Bunny83, Emanx140 and jacobattwobulls like this.
  27. ggmobi

    ggmobi

    Joined:
    Dec 9, 2015
    Posts:
    5
    Hey @Quatum1000,

    Thanks for the great algorithm. Could you by any chance point me for the right direction for calculating the minimium padding around the texture procedurally so that the blur wont overflow the original texture.