text: #import "GPUImageSingleComponentGaussianBlurFilter.h"
  
  @implementation GPUImageSingleComponentGaussianBlurFilter
  
  + (NSString *)vertexShaderForOptimizedBlurOfRadius:(NSUInteger)blurRadius sigma:(CGFloat)sigma;
  {
      if (blurRadius < 1)
      {
          return kGPUImageVertexShaderString;
      }
  
      // First, generate the normal Gaussian weights for a given sigma
      GLfloat *standardGaussianWeights = calloc(blurRadius + 1, sizeof(GLfloat));
      GLfloat sumOfWeights = 0.0;
      for (NSUInteger currentGaussianWeightIndex = 0; currentGaussianWeightIndex < blurRadius + 1; currentGaussianWeightIndex++)
      {
          standardGaussianWeights[currentGaussianWeightIndex] = (1.0 / sqrt(2.0 * M_PI * pow(sigma, 2.0))) * exp(-pow(currentGaussianWeightIndex, 2.0) / (2.0 * pow(sigma, 2.0)));
          
          if (currentGaussianWeightIndex == 0)
          {
              sumOfWeights += standardGaussianWeights[currentGaussianWeightIndex];
          }
          else
          {
              sumOfWeights += 2.0 * standardGaussianWeights[currentGaussianWeightIndex];
          }
      }
      
      // Next, normalize these weights to prevent the clipping of the Gaussian curve at the end of the discrete samples from reducing luminance
      for (NSUInteger currentGaussianWeightIndex = 0; currentGaussianWeightIndex < blurRadius + 1; currentGaussianWeightIndex++)
      {
          standardGaussianWeights[currentGaussianWeightIndex] = standardGaussianWeights[currentGaussianWeightIndex] / sumOfWeights;
      }
      
      // From these weights we calculate the offsets to read interpolated values from
      NSUInteger numberOfOptimizedOffsets = MIN(blurRadius / 2 + (blurRadius % 2), 7);
      GLfloat *optimizedGaussianOffsets = calloc(numberOfOptimizedOffsets, sizeof(GLfloat));
      
      for (NSUInteger currentOptimizedOffset = 0; currentOptimizedOffset < numberOfOptimizedOffsets; currentOptimizedOffset++)
      {
          GLfloat firstWeight = standardGaussianWeights[currentOptimizedOffset*2 + 1];
          GLfloat secondWeight = standardGaussianWeights[currentOptimizedOffset*2 + 2];
          
          GLfloat optimizedWeight = firstWeight + secondWeight;
          
          optimizedGaussianOffsets[currentOptimizedOffset] = (firstWeight * (currentOptimizedOffset*2 + 1) + secondWeight * (currentOptimizedOffset*2 + 2)) / optimizedWeight;
      }
      
      NSMutableString *shaderString = [[NSMutableString alloc] init];
      // Header
      [shaderString appendFormat:@"\
       attribute vec4 position;\n\
       attribute vec4 inputTextureCoordinate;\n\
       \n\
       uniform float texelWidthOffset;\n\
       uniform float texelHeightOffset;\n\
       \n\
       varying vec2 blurCoordinates[%lu];\n\
       \n\
       void main()\n\
       {\n\
       gl_Position = position;\n\
       \n\
       vec2 singleStepOffset = vec2(texelWidthOffset, texelHeightOffset);\n", (unsigned long)(1 + (numberOfOptimizedOffsets * 2))];
      
      // Inner offset loop
      [shaderString appendString:@"blurCoordinates[0] = inputTextureCoordinate.xy;\n"];
      for (NSUInteger currentOptimizedOffset = 0; currentOptimizedOffset < numberOfOptimizedOffsets; currentOptimizedOffset++)
      {
          [shaderString appendFormat:@"\
           blurCoordinates[%lu] = inputTextureCoordinate.xy + singleStepOffset * %f;\n\
           blurCoordinates[%lu] = inputTextureCoordinate.xy - singleStepOffset * %f;\n", (unsigned long)((currentOptimizedOffset * 2) + 1), optimizedGaussianOffsets[currentOptimizedOffset], (unsigned long)((currentOptimizedOffset * 2) + 2), optimizedGaussianOffsets[currentOptimizedOffset]];
      }
      
      // Footer
      [shaderString appendString:@"}\n"];
      
      free(optimizedGaussianOffsets);
      free(standardGaussianWeights);
      return shaderString;
  }
  
  + (NSString *)fragmentShaderForOptimizedBlurOfRadius:(NSUInteger)blurRadius sigma:(CGFloat)sigma;
  {
      if (blurRadius < 1)
      {
          return kGPUImagePassthroughFragmentShaderString;
      }
  
      // First, generate the normal Gaussian weights for a given sigma
      GLfloat *standardGaussianWeights = calloc(blurRadius + 1, sizeof(GLfloat));
      GLfloat sumOfWeights = 0.0;
      for (NSUInteger currentGaussianWeightIndex = 0; currentGaussianWeightIndex < blurRadius + 1; currentGaussianWeightIndex++)
      {
          standardGaussianWeights[currentGaussianWeightIndex] = (1.0 / sqrt(2.0 * M_PI * pow(sigma, 2.0))) * exp(-pow(currentGaussianWeightIndex, 2.0) / (2.0 * pow(sigma, 2.0)));
          
          if (currentGaussianWeightIndex == 0)
          {
              sumOfWeights += standardGaussianWeights[currentGaussianWeightIndex];
          }
          else
          {
              sumOfWeights += 2.0 * standardGaussianWeights[currentGaussianWeightIndex];
          }
      }
      
      // Next, normalize these weights to prevent the clipping of the Gaussian curve at the end of the discrete samples from reducing luminance
      for (NSUInteger currentGaussianWeightIndex = 0; currentGaussianWeightIndex < blurRadius + 1; currentGaussianWeightIndex++)
      {
          standardGaussianWeights[currentGaussianWeightIndex] = standardGaussianWeights[currentGaussianWeightIndex] / sumOfWeights;
      }
      
      // From these weights we calculate the offsets to read interpolated values from
      NSUInteger numberOfOptimizedOffsets = MIN(blurRadius / 2 + (blurRadius % 2), 7);
      NSUInteger trueNumberOfOptimizedOffsets = blurRadius / 2 + (blurRadius % 2);
      
      NSMutableString *shaderString = [[NSMutableString alloc] init];
      
      // Header
  #if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE
      [shaderString appendFormat:@"\
       uniform sampler2D inputImageTexture;\n\
       uniform highp float texelWidthOffset;\n\
       uniform highp float texelHeightOffset;\n\
       \n\
       varying highp vec2 blurCoordinates[%lu];\n\
       \n\
       void main()\n\
       {\n\
       lowp float sum = 0.0;\n", (unsigned long)(1 + (numberOfOptimizedOffsets * 2)) ];
  #else
      [shaderString appendFormat:@"\
       uniform sampler2D inputImageTexture;\n\
       uniform float texelWidthOffset;\n\
       uniform float texelHeightOffset;\n\
       \n\
       varying vec2 blurCoordinates[%lu];\n\
       \n\
       void main()\n\
       {\n\
       float sum = 0.0;\n", 1 + (numberOfOptimizedOffsets * 2) ];
  #endif
      
      // Inner texture loop
      [shaderString appendFormat:@"sum += texture2D(inputImageTexture, blurCoordinates[0]).r * %f;\n", standardGaussianWeights[0]];
      
      for (NSUInteger currentBlurCoordinateIndex = 0; currentBlurCoordinateIndex < numberOfOptimizedOffsets; currentBlurCoordinateIndex++)
      {
          GLfloat firstWeight = standardGaussianWeights[currentBlurCoordinateIndex * 2 + 1];
          GLfloat secondWeight = standardGaussianWeights[currentBlurCoordinateIndex * 2 + 2];
          GLfloat optimizedWeight = firstWeight + secondWeight;
          
          [shaderString appendFormat:@"sum += texture2D(inputImageTexture, blurCoordinates[%lu]).r * %f;\n", (unsigned long)((currentBlurCoordinateIndex * 2) + 1), optimizedWeight];
          [shaderString appendFormat:@"sum += texture2D(inputImageTexture, blurCoordinates[%lu]).r * %f;\n", (unsigned long)((currentBlurCoordinateIndex * 2) + 2), optimizedWeight];
      }
      
      // If the number of required samples exceeds the amount we can pass in via varyings, we have to do dependent texture reads in the fragment shader
      if (trueNumberOfOptimizedOffsets > numberOfOptimizedOffsets)
      {
  #if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE
          [shaderString appendString:@"highp vec2 singleStepOffset = vec2(texelWidthOffset, texelHeightOffset);\n"];
  #else
          [shaderString appendString:@"highp vec2 singleStepOffset = vec2(texelWidthOffset, texelHeightOffset);\n"];
  #endif
          
          for (NSUInteger currentOverlowTextureRead = numberOfOptimizedOffsets; currentOverlowTextureRead < trueNumberOfOptimizedOffsets; currentOverlowTextureRead++)
          {
              GLfloat firstWeight = standardGaussianWeights[currentOverlowTextureRead * 2 + 1];
              GLfloat secondWeight = standardGaussianWeights[currentOverlowTextureRead * 2 + 2];
              
              GLfloat optimizedWeight = firstWeight + secondWeight;
              GLfloat optimizedOffset = (firstWeight * (currentOverlowTextureRead * 2 + 1) + secondWeight * (currentOverlowTextureRead * 2 + 2)) / optimizedWeight;
              
              [shaderString appendFormat:@"sum += texture2D(inputImageTexture, blurCoordinates[0] + singleStepOffset * %f).r * %f;\n", optimizedOffset, optimizedWeight];
              [shaderString appendFormat:@"sum += texture2D(inputImageTexture, blurCoordinates[0] - singleStepOffset * %f).r * %f;\n", optimizedOffset, optimizedWeight];
          }
      }
      
      // Footer
      [shaderString appendString:@"\
       gl_FragColor = vec4(sum, sum, sum, 1.0);\n\
       }\n"];
      
      free(standardGaussianWeights);
      return shaderString;
  }
  
  
  @end
  