PageRenderTime 27ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 0ms

/Dependencies/GPUImage/Source/GPUImageGaussianBlurFilter.m

https://gitlab.com/Mr.Tomato/VideoEffects
Objective C | 488 lines | 374 code | 82 blank | 32 comment | 41 complexity | 5d921bafed6e4d35cb4507e826860e42 MD5 | raw file
  1. #import "GPUImageGaussianBlurFilter.h"
  2. @implementation GPUImageGaussianBlurFilter
  3. @synthesize texelSpacingMultiplier = _texelSpacingMultiplier;
  4. @synthesize blurRadiusInPixels = _blurRadiusInPixels;
  5. @synthesize blurRadiusAsFractionOfImageWidth = _blurRadiusAsFractionOfImageWidth;
  6. @synthesize blurRadiusAsFractionOfImageHeight = _blurRadiusAsFractionOfImageHeight;
  7. @synthesize blurPasses = _blurPasses;
  8. #pragma mark -
  9. #pragma mark Initialization and teardown
  10. - (id)initWithFirstStageVertexShaderFromString:(NSString *)firstStageVertexShaderString firstStageFragmentShaderFromString:(NSString *)firstStageFragmentShaderString secondStageVertexShaderFromString:(NSString *)secondStageVertexShaderString secondStageFragmentShaderFromString:(NSString *)secondStageFragmentShaderString
  11. {
  12. if (!(self = [super initWithFirstStageVertexShaderFromString:firstStageVertexShaderString firstStageFragmentShaderFromString:firstStageFragmentShaderString secondStageVertexShaderFromString:secondStageVertexShaderString secondStageFragmentShaderFromString:secondStageFragmentShaderString]))
  13. {
  14. return nil;
  15. }
  16. self.texelSpacingMultiplier = 1.0;
  17. _blurRadiusInPixels = 2.0;
  18. shouldResizeBlurRadiusWithImageSize = NO;
  19. return self;
  20. }
  21. - (id)init;
  22. {
  23. NSString *currentGaussianBlurVertexShader = [[self class] vertexShaderForOptimizedBlurOfRadius:4 sigma:2.0];
  24. NSString *currentGaussianBlurFragmentShader = [[self class] fragmentShaderForOptimizedBlurOfRadius:4 sigma:2.0];
  25. return [self initWithFirstStageVertexShaderFromString:currentGaussianBlurVertexShader firstStageFragmentShaderFromString:currentGaussianBlurFragmentShader secondStageVertexShaderFromString:currentGaussianBlurVertexShader secondStageFragmentShaderFromString:currentGaussianBlurFragmentShader];
  26. }
  27. #pragma mark -
  28. #pragma mark Auto-generation of optimized Gaussian shaders
  29. // "Implementation limit of 32 varying components exceeded" - Max number of varyings for these GPUs
  30. + (NSString *)vertexShaderForStandardBlurOfRadius:(NSUInteger)blurRadius sigma:(CGFloat)sigma;
  31. {
  32. if (blurRadius < 1)
  33. {
  34. return kGPUImageVertexShaderString;
  35. }
  36. // NSLog(@"Max varyings: %d", [GPUImageContext maximumVaryingVectorsForThisDevice]);
  37. NSMutableString *shaderString = [[NSMutableString alloc] init];
  38. // Header
  39. [shaderString appendFormat:@"\
  40. attribute vec4 position;\n\
  41. attribute vec4 inputTextureCoordinate;\n\
  42. \n\
  43. uniform float texelWidthOffset;\n\
  44. uniform float texelHeightOffset;\n\
  45. \n\
  46. varying vec2 blurCoordinates[%lu];\n\
  47. \n\
  48. void main()\n\
  49. {\n\
  50. gl_Position = position;\n\
  51. \n\
  52. vec2 singleStepOffset = vec2(texelWidthOffset, texelHeightOffset);\n", (unsigned long)(blurRadius * 2 + 1) ];
  53. // Inner offset loop
  54. for (NSUInteger currentBlurCoordinateIndex = 0; currentBlurCoordinateIndex < (blurRadius * 2 + 1); currentBlurCoordinateIndex++)
  55. {
  56. NSInteger offsetFromCenter = currentBlurCoordinateIndex - blurRadius;
  57. if (offsetFromCenter < 0)
  58. {
  59. [shaderString appendFormat:@"blurCoordinates[%ld] = inputTextureCoordinate.xy - singleStepOffset * %f;\n", (unsigned long)currentBlurCoordinateIndex, (GLfloat)(-offsetFromCenter)];
  60. }
  61. else if (offsetFromCenter > 0)
  62. {
  63. [shaderString appendFormat:@"blurCoordinates[%ld] = inputTextureCoordinate.xy + singleStepOffset * %f;\n", (unsigned long)currentBlurCoordinateIndex, (GLfloat)(offsetFromCenter)];
  64. }
  65. else
  66. {
  67. [shaderString appendFormat:@"blurCoordinates[%ld] = inputTextureCoordinate.xy;\n", (unsigned long)currentBlurCoordinateIndex];
  68. }
  69. }
  70. // Footer
  71. [shaderString appendString:@"}\n"];
  72. return shaderString;
  73. }
  74. + (NSString *)fragmentShaderForStandardBlurOfRadius:(NSUInteger)blurRadius sigma:(CGFloat)sigma;
  75. {
  76. if (blurRadius < 1)
  77. {
  78. return kGPUImagePassthroughFragmentShaderString;
  79. }
  80. // First, generate the normal Gaussian weights for a given sigma
  81. GLfloat *standardGaussianWeights = calloc(blurRadius + 1, sizeof(GLfloat));
  82. GLfloat sumOfWeights = 0.0;
  83. for (NSUInteger currentGaussianWeightIndex = 0; currentGaussianWeightIndex < blurRadius + 1; currentGaussianWeightIndex++)
  84. {
  85. standardGaussianWeights[currentGaussianWeightIndex] = (1.0 / sqrt(2.0 * M_PI * pow(sigma, 2.0))) * exp(-pow(currentGaussianWeightIndex, 2.0) / (2.0 * pow(sigma, 2.0)));
  86. if (currentGaussianWeightIndex == 0)
  87. {
  88. sumOfWeights += standardGaussianWeights[currentGaussianWeightIndex];
  89. }
  90. else
  91. {
  92. sumOfWeights += 2.0 * standardGaussianWeights[currentGaussianWeightIndex];
  93. }
  94. }
  95. // Next, normalize these weights to prevent the clipping of the Gaussian curve at the end of the discrete samples from reducing luminance
  96. for (NSUInteger currentGaussianWeightIndex = 0; currentGaussianWeightIndex < blurRadius + 1; currentGaussianWeightIndex++)
  97. {
  98. standardGaussianWeights[currentGaussianWeightIndex] = standardGaussianWeights[currentGaussianWeightIndex] / sumOfWeights;
  99. }
  100. // Finally, generate the shader from these weights
  101. NSMutableString *shaderString = [[NSMutableString alloc] init];
  102. // Header
  103. #if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE
  104. [shaderString appendFormat:@"\
  105. uniform sampler2D inputImageTexture;\n\
  106. \n\
  107. varying highp vec2 blurCoordinates[%lu];\n\
  108. \n\
  109. void main()\n\
  110. {\n\
  111. lowp vec4 sum = vec4(0.0);\n", (unsigned long)(blurRadius * 2 + 1) ];
  112. #else
  113. [shaderString appendFormat:@"\
  114. uniform sampler2D inputImageTexture;\n\
  115. \n\
  116. varying vec2 blurCoordinates[%lu];\n\
  117. \n\
  118. void main()\n\
  119. {\n\
  120. vec4 sum = vec4(0.0);\n", (blurRadius * 2 + 1) ];
  121. #endif
  122. // Inner texture loop
  123. for (NSUInteger currentBlurCoordinateIndex = 0; currentBlurCoordinateIndex < (blurRadius * 2 + 1); currentBlurCoordinateIndex++)
  124. {
  125. NSInteger offsetFromCenter = currentBlurCoordinateIndex - blurRadius;
  126. if (offsetFromCenter < 0)
  127. {
  128. [shaderString appendFormat:@"sum += texture2D(inputImageTexture, blurCoordinates[%lu]) * %f;\n", (unsigned long)currentBlurCoordinateIndex, standardGaussianWeights[-offsetFromCenter]];
  129. }
  130. else
  131. {
  132. [shaderString appendFormat:@"sum += texture2D(inputImageTexture, blurCoordinates[%lu]) * %f;\n", (unsigned long)currentBlurCoordinateIndex, standardGaussianWeights[offsetFromCenter]];
  133. }
  134. }
  135. // Footer
  136. [shaderString appendString:@"\
  137. gl_FragColor = sum;\n\
  138. }\n"];
  139. free(standardGaussianWeights);
  140. return shaderString;
  141. }
  142. + (NSString *)vertexShaderForOptimizedBlurOfRadius:(NSUInteger)blurRadius sigma:(CGFloat)sigma;
  143. {
  144. if (blurRadius < 1)
  145. {
  146. return kGPUImageVertexShaderString;
  147. }
  148. // First, generate the normal Gaussian weights for a given sigma
  149. GLfloat *standardGaussianWeights = calloc(blurRadius + 1, sizeof(GLfloat));
  150. GLfloat sumOfWeights = 0.0;
  151. for (NSUInteger currentGaussianWeightIndex = 0; currentGaussianWeightIndex < blurRadius + 1; currentGaussianWeightIndex++)
  152. {
  153. standardGaussianWeights[currentGaussianWeightIndex] = (1.0 / sqrt(2.0 * M_PI * pow(sigma, 2.0))) * exp(-pow(currentGaussianWeightIndex, 2.0) / (2.0 * pow(sigma, 2.0)));
  154. if (currentGaussianWeightIndex == 0)
  155. {
  156. sumOfWeights += standardGaussianWeights[currentGaussianWeightIndex];
  157. }
  158. else
  159. {
  160. sumOfWeights += 2.0 * standardGaussianWeights[currentGaussianWeightIndex];
  161. }
  162. }
  163. // Next, normalize these weights to prevent the clipping of the Gaussian curve at the end of the discrete samples from reducing luminance
  164. for (NSUInteger currentGaussianWeightIndex = 0; currentGaussianWeightIndex < blurRadius + 1; currentGaussianWeightIndex++)
  165. {
  166. standardGaussianWeights[currentGaussianWeightIndex] = standardGaussianWeights[currentGaussianWeightIndex] / sumOfWeights;
  167. }
  168. // From these weights we calculate the offsets to read interpolated values from
  169. NSUInteger numberOfOptimizedOffsets = MIN(blurRadius / 2 + (blurRadius % 2), 7);
  170. GLfloat *optimizedGaussianOffsets = calloc(numberOfOptimizedOffsets, sizeof(GLfloat));
  171. for (NSUInteger currentOptimizedOffset = 0; currentOptimizedOffset < numberOfOptimizedOffsets; currentOptimizedOffset++)
  172. {
  173. GLfloat firstWeight = standardGaussianWeights[currentOptimizedOffset*2 + 1];
  174. GLfloat secondWeight = standardGaussianWeights[currentOptimizedOffset*2 + 2];
  175. GLfloat optimizedWeight = firstWeight + secondWeight;
  176. optimizedGaussianOffsets[currentOptimizedOffset] = (firstWeight * (currentOptimizedOffset*2 + 1) + secondWeight * (currentOptimizedOffset*2 + 2)) / optimizedWeight;
  177. }
  178. NSMutableString *shaderString = [[NSMutableString alloc] init];
  179. // Header
  180. [shaderString appendFormat:@"\
  181. attribute vec4 position;\n\
  182. attribute vec4 inputTextureCoordinate;\n\
  183. \n\
  184. uniform float texelWidthOffset;\n\
  185. uniform float texelHeightOffset;\n\
  186. \n\
  187. varying vec2 blurCoordinates[%lu];\n\
  188. \n\
  189. void main()\n\
  190. {\n\
  191. gl_Position = position;\n\
  192. \n\
  193. vec2 singleStepOffset = vec2(texelWidthOffset, texelHeightOffset);\n", (unsigned long)(1 + (numberOfOptimizedOffsets * 2))];
  194. // Inner offset loop
  195. [shaderString appendString:@"blurCoordinates[0] = inputTextureCoordinate.xy;\n"];
  196. for (NSUInteger currentOptimizedOffset = 0; currentOptimizedOffset < numberOfOptimizedOffsets; currentOptimizedOffset++)
  197. {
  198. [shaderString appendFormat:@"\
  199. blurCoordinates[%lu] = inputTextureCoordinate.xy + singleStepOffset * %f;\n\
  200. blurCoordinates[%lu] = inputTextureCoordinate.xy - singleStepOffset * %f;\n", (unsigned long)((currentOptimizedOffset * 2) + 1), optimizedGaussianOffsets[currentOptimizedOffset], (unsigned long)((currentOptimizedOffset * 2) + 2), optimizedGaussianOffsets[currentOptimizedOffset]];
  201. }
  202. // Footer
  203. [shaderString appendString:@"}\n"];
  204. free(optimizedGaussianOffsets);
  205. free(standardGaussianWeights);
  206. return shaderString;
  207. }
  208. + (NSString *)fragmentShaderForOptimizedBlurOfRadius:(NSUInteger)blurRadius sigma:(CGFloat)sigma;
  209. {
  210. if (blurRadius < 1)
  211. {
  212. return kGPUImagePassthroughFragmentShaderString;
  213. }
  214. // First, generate the normal Gaussian weights for a given sigma
  215. GLfloat *standardGaussianWeights = calloc(blurRadius + 1, sizeof(GLfloat));
  216. GLfloat sumOfWeights = 0.0;
  217. for (NSUInteger currentGaussianWeightIndex = 0; currentGaussianWeightIndex < blurRadius + 1; currentGaussianWeightIndex++)
  218. {
  219. standardGaussianWeights[currentGaussianWeightIndex] = (1.0 / sqrt(2.0 * M_PI * pow(sigma, 2.0))) * exp(-pow(currentGaussianWeightIndex, 2.0) / (2.0 * pow(sigma, 2.0)));
  220. if (currentGaussianWeightIndex == 0)
  221. {
  222. sumOfWeights += standardGaussianWeights[currentGaussianWeightIndex];
  223. }
  224. else
  225. {
  226. sumOfWeights += 2.0 * standardGaussianWeights[currentGaussianWeightIndex];
  227. }
  228. }
  229. // Next, normalize these weights to prevent the clipping of the Gaussian curve at the end of the discrete samples from reducing luminance
  230. for (NSUInteger currentGaussianWeightIndex = 0; currentGaussianWeightIndex < blurRadius + 1; currentGaussianWeightIndex++)
  231. {
  232. standardGaussianWeights[currentGaussianWeightIndex] = standardGaussianWeights[currentGaussianWeightIndex] / sumOfWeights;
  233. }
  234. // From these weights we calculate the offsets to read interpolated values from
  235. NSUInteger numberOfOptimizedOffsets = MIN(blurRadius / 2 + (blurRadius % 2), 7);
  236. NSUInteger trueNumberOfOptimizedOffsets = blurRadius / 2 + (blurRadius % 2);
  237. NSMutableString *shaderString = [[NSMutableString alloc] init];
  238. // Header
  239. #if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE
  240. [shaderString appendFormat:@"\
  241. uniform sampler2D inputImageTexture;\n\
  242. uniform highp float texelWidthOffset;\n\
  243. uniform highp float texelHeightOffset;\n\
  244. \n\
  245. varying highp vec2 blurCoordinates[%lu];\n\
  246. \n\
  247. void main()\n\
  248. {\n\
  249. lowp vec4 sum = vec4(0.0);\n", (unsigned long)(1 + (numberOfOptimizedOffsets * 2)) ];
  250. #else
  251. [shaderString appendFormat:@"\
  252. uniform sampler2D inputImageTexture;\n\
  253. uniform float texelWidthOffset;\n\
  254. uniform float texelHeightOffset;\n\
  255. \n\
  256. varying vec2 blurCoordinates[%lu];\n\
  257. \n\
  258. void main()\n\
  259. {\n\
  260. vec4 sum = vec4(0.0);\n", 1 + (numberOfOptimizedOffsets * 2) ];
  261. #endif
  262. // Inner texture loop
  263. [shaderString appendFormat:@"sum += texture2D(inputImageTexture, blurCoordinates[0]) * %f;\n", standardGaussianWeights[0]];
  264. for (NSUInteger currentBlurCoordinateIndex = 0; currentBlurCoordinateIndex < numberOfOptimizedOffsets; currentBlurCoordinateIndex++)
  265. {
  266. GLfloat firstWeight = standardGaussianWeights[currentBlurCoordinateIndex * 2 + 1];
  267. GLfloat secondWeight = standardGaussianWeights[currentBlurCoordinateIndex * 2 + 2];
  268. GLfloat optimizedWeight = firstWeight + secondWeight;
  269. [shaderString appendFormat:@"sum += texture2D(inputImageTexture, blurCoordinates[%lu]) * %f;\n", (unsigned long)((currentBlurCoordinateIndex * 2) + 1), optimizedWeight];
  270. [shaderString appendFormat:@"sum += texture2D(inputImageTexture, blurCoordinates[%lu]) * %f;\n", (unsigned long)((currentBlurCoordinateIndex * 2) + 2), optimizedWeight];
  271. }
  272. // 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
  273. if (trueNumberOfOptimizedOffsets > numberOfOptimizedOffsets)
  274. {
  275. #if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE
  276. [shaderString appendString:@"highp vec2 singleStepOffset = vec2(texelWidthOffset, texelHeightOffset);\n"];
  277. #else
  278. [shaderString appendString:@"vec2 singleStepOffset = vec2(texelWidthOffset, texelHeightOffset);\n"];
  279. #endif
  280. for (NSUInteger currentOverlowTextureRead = numberOfOptimizedOffsets; currentOverlowTextureRead < trueNumberOfOptimizedOffsets; currentOverlowTextureRead++)
  281. {
  282. GLfloat firstWeight = standardGaussianWeights[currentOverlowTextureRead * 2 + 1];
  283. GLfloat secondWeight = standardGaussianWeights[currentOverlowTextureRead * 2 + 2];
  284. GLfloat optimizedWeight = firstWeight + secondWeight;
  285. GLfloat optimizedOffset = (firstWeight * (currentOverlowTextureRead * 2 + 1) + secondWeight * (currentOverlowTextureRead * 2 + 2)) / optimizedWeight;
  286. [shaderString appendFormat:@"sum += texture2D(inputImageTexture, blurCoordinates[0] + singleStepOffset * %f) * %f;\n", optimizedOffset, optimizedWeight];
  287. [shaderString appendFormat:@"sum += texture2D(inputImageTexture, blurCoordinates[0] - singleStepOffset * %f) * %f;\n", optimizedOffset, optimizedWeight];
  288. }
  289. }
  290. // Footer
  291. [shaderString appendString:@"\
  292. gl_FragColor = sum;\n\
  293. }\n"];
  294. free(standardGaussianWeights);
  295. return shaderString;
  296. }
  297. - (void)setupFilterForSize:(CGSize)filterFrameSize;
  298. {
  299. [super setupFilterForSize:filterFrameSize];
  300. if (shouldResizeBlurRadiusWithImageSize == YES)
  301. {
  302. }
  303. }
  304. #pragma mark -
  305. #pragma mark Rendering
  306. - (void)renderToTextureWithVertices:(const GLfloat *)vertices textureCoordinates:(const GLfloat *)textureCoordinates;
  307. {
  308. [super renderToTextureWithVertices:vertices textureCoordinates:textureCoordinates];
  309. for (NSUInteger currentAdditionalBlurPass = 1; currentAdditionalBlurPass < _blurPasses; currentAdditionalBlurPass++)
  310. {
  311. [super renderToTextureWithVertices:vertices textureCoordinates:[[self class] textureCoordinatesForRotation:kGPUImageNoRotation]];
  312. }
  313. }
  314. - (void)switchToVertexShader:(NSString *)newVertexShader fragmentShader:(NSString *)newFragmentShader;
  315. {
  316. runSynchronouslyOnVideoProcessingQueue(^{
  317. [GPUImageContext useImageProcessingContext];
  318. filterProgram = [[GPUImageContext sharedImageProcessingContext] programForVertexShaderString:newVertexShader fragmentShaderString:newFragmentShader];
  319. if (!filterProgram.initialized)
  320. {
  321. [self initializeAttributes];
  322. if (![filterProgram link])
  323. {
  324. NSString *progLog = [filterProgram programLog];
  325. NSLog(@"Program link log: %@", progLog);
  326. NSString *fragLog = [filterProgram fragmentShaderLog];
  327. NSLog(@"Fragment shader compile log: %@", fragLog);
  328. NSString *vertLog = [filterProgram vertexShaderLog];
  329. NSLog(@"Vertex shader compile log: %@", vertLog);
  330. filterProgram = nil;
  331. NSAssert(NO, @"Filter shader link failed");
  332. }
  333. }
  334. filterPositionAttribute = [filterProgram attributeIndex:@"position"];
  335. filterTextureCoordinateAttribute = [filterProgram attributeIndex:@"inputTextureCoordinate"];
  336. filterInputTextureUniform = [filterProgram uniformIndex:@"inputImageTexture"]; // This does assume a name of "inputImageTexture" for the fragment shader
  337. verticalPassTexelWidthOffsetUniform = [filterProgram uniformIndex:@"texelWidthOffset"];
  338. verticalPassTexelHeightOffsetUniform = [filterProgram uniformIndex:@"texelHeightOffset"];
  339. [GPUImageContext setActiveShaderProgram:filterProgram];
  340. glEnableVertexAttribArray(filterPositionAttribute);
  341. glEnableVertexAttribArray(filterTextureCoordinateAttribute);
  342. secondFilterProgram = [[GPUImageContext sharedImageProcessingContext] programForVertexShaderString:newVertexShader fragmentShaderString:newFragmentShader];
  343. if (!secondFilterProgram.initialized)
  344. {
  345. [self initializeSecondaryAttributes];
  346. if (![secondFilterProgram link])
  347. {
  348. NSString *progLog = [secondFilterProgram programLog];
  349. NSLog(@"Program link log: %@", progLog);
  350. NSString *fragLog = [secondFilterProgram fragmentShaderLog];
  351. NSLog(@"Fragment shader compile log: %@", fragLog);
  352. NSString *vertLog = [secondFilterProgram vertexShaderLog];
  353. NSLog(@"Vertex shader compile log: %@", vertLog);
  354. secondFilterProgram = nil;
  355. NSAssert(NO, @"Filter shader link failed");
  356. }
  357. }
  358. secondFilterPositionAttribute = [secondFilterProgram attributeIndex:@"position"];
  359. secondFilterTextureCoordinateAttribute = [secondFilterProgram attributeIndex:@"inputTextureCoordinate"];
  360. secondFilterInputTextureUniform = [secondFilterProgram uniformIndex:@"inputImageTexture"]; // This does assume a name of "inputImageTexture" for the fragment shader
  361. secondFilterInputTextureUniform2 = [secondFilterProgram uniformIndex:@"inputImageTexture2"]; // This does assume a name of "inputImageTexture2" for second input texture in the fragment shader
  362. horizontalPassTexelWidthOffsetUniform = [secondFilterProgram uniformIndex:@"texelWidthOffset"];
  363. horizontalPassTexelHeightOffsetUniform = [secondFilterProgram uniformIndex:@"texelHeightOffset"];
  364. [GPUImageContext setActiveShaderProgram:secondFilterProgram];
  365. glEnableVertexAttribArray(secondFilterPositionAttribute);
  366. glEnableVertexAttribArray(secondFilterTextureCoordinateAttribute);
  367. [self setupFilterForSize:[self sizeOfFBO]];
  368. glFinish();
  369. });
  370. }
  371. #pragma mark -
  372. #pragma mark Accessors
  373. - (void)setTexelSpacingMultiplier:(CGFloat)newValue;
  374. {
  375. _texelSpacingMultiplier = newValue;
  376. _verticalTexelSpacing = _texelSpacingMultiplier;
  377. _horizontalTexelSpacing = _texelSpacingMultiplier;
  378. [self setupFilterForSize:[self sizeOfFBO]];
  379. }
  380. // inputRadius for Core Image's CIGaussianBlur is really sigma in the Gaussian equation, so I'm using that for my blur radius, to be consistent
  381. - (void)setBlurRadiusInPixels:(CGFloat)newValue;
  382. {
  383. // 7.0 is the limit for blur size for hardcoded varying offsets
  384. if (round(newValue) != _blurRadiusInPixels)
  385. {
  386. _blurRadiusInPixels = round(newValue); // For now, only do integral sigmas
  387. NSUInteger calculatedSampleRadius = 0;
  388. if (_blurRadiusInPixels >= 1) // Avoid a divide-by-zero error here
  389. {
  390. // Calculate the number of pixels to sample from by setting a bottom limit for the contribution of the outermost pixel
  391. CGFloat minimumWeightToFindEdgeOfSamplingArea = 1.0/256.0;
  392. calculatedSampleRadius = floor(sqrt(-2.0 * pow(_blurRadiusInPixels, 2.0) * log(minimumWeightToFindEdgeOfSamplingArea * sqrt(2.0 * M_PI * pow(_blurRadiusInPixels, 2.0))) ));
  393. calculatedSampleRadius += calculatedSampleRadius % 2; // There's nothing to gain from handling odd radius sizes, due to the optimizations I use
  394. }
  395. // NSLog(@"Blur radius: %f, calculated sample radius: %d", _blurRadiusInPixels, calculatedSampleRadius);
  396. //
  397. NSString *newGaussianBlurVertexShader = [[self class] vertexShaderForOptimizedBlurOfRadius:calculatedSampleRadius sigma:_blurRadiusInPixels];
  398. NSString *newGaussianBlurFragmentShader = [[self class] fragmentShaderForOptimizedBlurOfRadius:calculatedSampleRadius sigma:_blurRadiusInPixels];
  399. // NSLog(@"Optimized vertex shader: \n%@", newGaussianBlurVertexShader);
  400. // NSLog(@"Optimized fragment shader: \n%@", newGaussianBlurFragmentShader);
  401. //
  402. [self switchToVertexShader:newGaussianBlurVertexShader fragmentShader:newGaussianBlurFragmentShader];
  403. }
  404. shouldResizeBlurRadiusWithImageSize = NO;
  405. }
  406. @end