mirror of
https://github.com/danog/Telegram.git
synced 2024-12-02 17:38:07 +01:00
333 lines
10 KiB
Mathematica
333 lines
10 KiB
Mathematica
|
#import "PGPhotoEnhanceLUTGenerator.h"
|
||
|
|
||
|
#import "PGPhotoProcessPass.h"
|
||
|
|
||
|
const NSUInteger PGPhotoEnhanceHistogramBins = 256;
|
||
|
const NSUInteger PGPhotoEnhanceSegments = 4;
|
||
|
|
||
|
@interface PGPhotoEnhanceLUTGenerator ()
|
||
|
{
|
||
|
CGFloat _clipLimit;
|
||
|
|
||
|
CGSize _imageSize;
|
||
|
GPUImageRotationMode _inputRotation;
|
||
|
|
||
|
GPUImageFramebuffer *_firstInputFramebuffer;
|
||
|
GPUImageFramebuffer *_outputFramebuffer;
|
||
|
GPUImageFramebuffer *_retainedFramebuffer;
|
||
|
|
||
|
GLProgram *_dataProgram;
|
||
|
GLint _dataPositionAttribute;
|
||
|
GLint _dataTextureCoordinateAttribute;
|
||
|
GLint _dataInputTextureUniform;
|
||
|
|
||
|
bool _hasReadCurrentFrame;
|
||
|
|
||
|
GLubyte *_rawBytesForImage;
|
||
|
bool _lockNextFramebuffer;
|
||
|
}
|
||
|
|
||
|
@property (nonatomic, assign) BOOL enabled;
|
||
|
|
||
|
@end
|
||
|
|
||
|
@implementation PGPhotoEnhanceLUTGenerator
|
||
|
|
||
|
- (instancetype)init
|
||
|
{
|
||
|
self = [super init];
|
||
|
if (self != nil)
|
||
|
{
|
||
|
_clipLimit = 1.25f;
|
||
|
|
||
|
self.enabled = true;
|
||
|
_lockNextFramebuffer = false;
|
||
|
_inputRotation = kGPUImageNoRotation;
|
||
|
|
||
|
[GPUImageContext useImageProcessingContext];
|
||
|
|
||
|
if (([GPUImageContext supportsFastTextureUpload]))
|
||
|
{
|
||
|
_dataProgram = [[GPUImageContext sharedImageProcessingContext] programForVertexShaderString:kGPUImageVertexShaderString fragmentShaderString:PGPhotoEnhanceColorSwapShaderString];
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
_dataProgram = [[GPUImageContext sharedImageProcessingContext] programForVertexShaderString:kGPUImageVertexShaderString fragmentShaderString:kGPUImagePassthroughFragmentShaderString];
|
||
|
}
|
||
|
|
||
|
if (!_dataProgram.initialized)
|
||
|
{
|
||
|
[_dataProgram addAttribute:@"position"];
|
||
|
[_dataProgram addAttribute:@"inputTexCoord"];
|
||
|
|
||
|
if (![_dataProgram link])
|
||
|
{
|
||
|
NSString *progLog = [_dataProgram programLog];
|
||
|
NSLog(@"Program link log: %@", progLog);
|
||
|
NSString *fragLog = [_dataProgram fragmentShaderLog];
|
||
|
NSLog(@"Fragment shader compile log: %@", fragLog);
|
||
|
NSString *vertLog = [_dataProgram vertexShaderLog];
|
||
|
NSLog(@"Vertex shader compile log: %@", vertLog);
|
||
|
_dataProgram = nil;
|
||
|
NSAssert(NO, @"Filter shader link failed");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
_dataPositionAttribute = [_dataProgram attributeIndex:@"position"];
|
||
|
_dataTextureCoordinateAttribute = [_dataProgram attributeIndex:@"inputTexCoord"];
|
||
|
_dataInputTextureUniform = [_dataProgram uniformIndex:@"sourceImage"];
|
||
|
}
|
||
|
return self;
|
||
|
}
|
||
|
|
||
|
- (void)dealloc
|
||
|
{
|
||
|
if (_rawBytesForImage != NULL && (![GPUImageContext supportsFastTextureUpload]))
|
||
|
{
|
||
|
free(_rawBytesForImage);
|
||
|
_rawBytesForImage = NULL;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#pragma mark - GPUImageInput
|
||
|
|
||
|
- (void)newFrameReadyAtTime:(CMTime)__unused frameTime atIndex:(NSInteger)__unused textureIndex
|
||
|
{
|
||
|
if (self.skip)
|
||
|
return;
|
||
|
|
||
|
_hasReadCurrentFrame = false;
|
||
|
_lockNextFramebuffer = true;
|
||
|
|
||
|
NSUInteger totalSegments = PGPhotoEnhanceSegments * PGPhotoEnhanceSegments;
|
||
|
CGSize tileSize = CGSizeMake(CGFloor(_imageSize.width / PGPhotoEnhanceSegments), CGFloor(_imageSize.height / PGPhotoEnhanceSegments));
|
||
|
NSUInteger tileArea = (NSUInteger)(tileSize.width * tileSize.height);
|
||
|
NSUInteger clipLimit = (NSUInteger)MAX(1, _clipLimit * tileArea / (CGFloat)PGPhotoEnhanceHistogramBins);
|
||
|
CGFloat scale = 255.0f / (CGFloat)tileArea;
|
||
|
|
||
|
GLubyte *bytes = [self _rawBytes];
|
||
|
NSUInteger bytesPerRow = [_retainedFramebuffer bytesPerRow];
|
||
|
|
||
|
NSUInteger hist[totalSegments][PGPhotoEnhanceHistogramBins];
|
||
|
NSUInteger cdfs[totalSegments][PGPhotoEnhanceHistogramBins];
|
||
|
NSUInteger cdfsMin[totalSegments];
|
||
|
NSUInteger cdfsMax[totalSegments];
|
||
|
|
||
|
memset(hist, 0, totalSegments * PGPhotoEnhanceHistogramBins * sizeof(NSUInteger));
|
||
|
memset(cdfs, 0, totalSegments * PGPhotoEnhanceHistogramBins * sizeof(NSUInteger));
|
||
|
memset(cdfsMin, 0, totalSegments * sizeof(NSUInteger));
|
||
|
memset(cdfsMax, 0, totalSegments * sizeof(NSUInteger));
|
||
|
|
||
|
CGFloat xMul = PGPhotoEnhanceSegments / _imageSize.width;
|
||
|
CGFloat yMul = PGPhotoEnhanceSegments / _imageSize.height;
|
||
|
|
||
|
for (NSUInteger y = 0; y < _imageSize.height; y++)
|
||
|
{
|
||
|
NSUInteger yOffset = y * bytesPerRow;
|
||
|
for (NSUInteger x = 0; x < _imageSize.width; x++)
|
||
|
{
|
||
|
NSUInteger index = x * 4 + yOffset;
|
||
|
|
||
|
NSUInteger tx = (NSUInteger)(x * xMul);
|
||
|
NSUInteger ty = (NSUInteger)(y * yMul);
|
||
|
NSUInteger t = ty * PGPhotoEnhanceSegments + tx;
|
||
|
|
||
|
GLubyte value = bytes[index + 2];
|
||
|
hist[t][value]++;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
[_retainedFramebuffer unlockAfterReading];
|
||
|
[_retainedFramebuffer unlock];
|
||
|
_retainedFramebuffer = nil;
|
||
|
|
||
|
for (NSUInteger i = 0; i < totalSegments; i++)
|
||
|
{
|
||
|
if (clipLimit > 0)
|
||
|
{
|
||
|
NSUInteger clipped = 0;
|
||
|
for (NSUInteger j = 0; j < PGPhotoEnhanceHistogramBins; ++j)
|
||
|
{
|
||
|
if (hist[i][j] > clipLimit)
|
||
|
{
|
||
|
clipped += hist[i][j] - clipLimit;
|
||
|
hist[i][j] = clipLimit;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
NSUInteger redistBatch = clipped / PGPhotoEnhanceHistogramBins;
|
||
|
NSUInteger residual = clipped - redistBatch * PGPhotoEnhanceHistogramBins;
|
||
|
|
||
|
for (NSUInteger j = 0; j < PGPhotoEnhanceHistogramBins; ++j)
|
||
|
hist[i][j] += redistBatch;
|
||
|
|
||
|
for (NSUInteger j = 0; j < residual; ++j)
|
||
|
hist[i][j]++;
|
||
|
}
|
||
|
|
||
|
memcpy(&cdfs[i], &hist[i], PGPhotoEnhanceHistogramBins * sizeof(NSUInteger));
|
||
|
|
||
|
NSUInteger hMin = PGPhotoEnhanceHistogramBins - 1;
|
||
|
for (NSUInteger j = 0; j < hMin; ++j)
|
||
|
{
|
||
|
if (cdfs[j] != 0)
|
||
|
hMin = j;
|
||
|
}
|
||
|
|
||
|
NSUInteger cdf = 0;
|
||
|
for (NSUInteger j = hMin; j < PGPhotoEnhanceHistogramBins; ++j)
|
||
|
{
|
||
|
cdf += cdfs[i][j];
|
||
|
cdfs[i][j] = (uint8_t)MIN(255, cdf * scale);
|
||
|
}
|
||
|
|
||
|
cdfsMin[i] = cdfs[i][hMin];
|
||
|
cdfsMax[i] = cdfs[i][PGPhotoEnhanceHistogramBins - 1];
|
||
|
}
|
||
|
|
||
|
NSUInteger resultSize = 4 * PGPhotoEnhanceHistogramBins * totalSegments;
|
||
|
NSUInteger resultBytesPerRow = 4 * PGPhotoEnhanceHistogramBins;
|
||
|
|
||
|
GLubyte *result = calloc(resultSize, sizeof(GLubyte));
|
||
|
for (NSUInteger tile = 0; tile < totalSegments; tile++)
|
||
|
{
|
||
|
NSUInteger yOffset = tile * resultBytesPerRow;
|
||
|
for (NSUInteger i = 0; i < PGPhotoEnhanceHistogramBins; i++)
|
||
|
{
|
||
|
NSUInteger index = i * 4 + yOffset;
|
||
|
result[index] = (uint8_t)cdfs[tile][i];
|
||
|
result[index + 1] = (uint8_t)cdfsMin[tile];
|
||
|
result[index + 2] = (uint8_t)cdfsMax[tile];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (self.lutDataReady != nil)
|
||
|
self.lutDataReady(result);
|
||
|
}
|
||
|
|
||
|
- (NSInteger)nextAvailableTextureIndex
|
||
|
{
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
- (void)setInputFramebuffer:(GPUImageFramebuffer *)newInputFramebuffer atIndex:(NSInteger)__unused textureIndex
|
||
|
{
|
||
|
_firstInputFramebuffer = newInputFramebuffer;
|
||
|
[_firstInputFramebuffer lock];
|
||
|
}
|
||
|
|
||
|
- (void)setInputRotation:(GPUImageRotationMode)newInputRotation atIndex:(NSInteger)__unused textureIndex
|
||
|
{
|
||
|
_inputRotation = newInputRotation;
|
||
|
}
|
||
|
|
||
|
- (void)setInputSize:(CGSize)newSize atIndex:(NSInteger)__unused textureIndex
|
||
|
{
|
||
|
_imageSize = newSize;
|
||
|
}
|
||
|
|
||
|
- (CGSize)maximumOutputSize
|
||
|
{
|
||
|
return CGSizeMake(PGPhotoEnhanceHistogramBins, PGPhotoEnhanceSegments * PGPhotoEnhanceSegments);
|
||
|
}
|
||
|
|
||
|
- (void)endProcessing
|
||
|
{
|
||
|
|
||
|
}
|
||
|
|
||
|
- (BOOL)shouldIgnoreUpdatesToThisTarget
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
- (BOOL)wantsMonochromeInput
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
- (void)setCurrentlyReceivingMonochromeInput:(BOOL)__unused newValue
|
||
|
{
|
||
|
|
||
|
}
|
||
|
|
||
|
- (void)_render
|
||
|
{
|
||
|
[GPUImageContext setActiveShaderProgram:_dataProgram];
|
||
|
|
||
|
_outputFramebuffer = [[GPUImageContext sharedFramebufferCache] fetchFramebufferForSize:_imageSize onlyTexture:false];
|
||
|
[_outputFramebuffer activateFramebuffer];
|
||
|
|
||
|
if(_lockNextFramebuffer)
|
||
|
{
|
||
|
_retainedFramebuffer = _outputFramebuffer;
|
||
|
[_retainedFramebuffer lock];
|
||
|
[_retainedFramebuffer lockForReading];
|
||
|
_lockNextFramebuffer = false;
|
||
|
}
|
||
|
|
||
|
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
|
||
|
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||
|
|
||
|
static const GLfloat squareVertices[] = {
|
||
|
-1.0f, -1.0f,
|
||
|
1.0f, -1.0f,
|
||
|
-1.0f, 1.0f,
|
||
|
1.0f, 1.0f,
|
||
|
};
|
||
|
|
||
|
static const GLfloat textureCoordinates[] = {
|
||
|
0.0f, 0.0f,
|
||
|
1.0f, 0.0f,
|
||
|
0.0f, 1.0f,
|
||
|
1.0f, 1.0f,
|
||
|
};
|
||
|
|
||
|
glActiveTexture(GL_TEXTURE4);
|
||
|
glBindTexture(GL_TEXTURE_2D, [_firstInputFramebuffer texture]);
|
||
|
glUniform1i(_dataInputTextureUniform, 4);
|
||
|
|
||
|
glVertexAttribPointer(_dataPositionAttribute, 2, GL_FLOAT, 0, 0, squareVertices);
|
||
|
glVertexAttribPointer(_dataTextureCoordinateAttribute, 2, GL_FLOAT, 0, 0, textureCoordinates);
|
||
|
|
||
|
glEnableVertexAttribArray(_dataPositionAttribute);
|
||
|
glEnableVertexAttribArray(_dataTextureCoordinateAttribute);
|
||
|
|
||
|
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
|
||
|
[_firstInputFramebuffer unlock];
|
||
|
}
|
||
|
|
||
|
- (GLubyte *)_rawBytes
|
||
|
{
|
||
|
if ((_rawBytesForImage == NULL) && (![GPUImageContext supportsFastTextureUpload]))
|
||
|
{
|
||
|
_rawBytesForImage = (GLubyte *)calloc((NSInteger)(_imageSize.width * _imageSize.height) * 4, sizeof(GLubyte));
|
||
|
_hasReadCurrentFrame = false;
|
||
|
}
|
||
|
|
||
|
if (!_hasReadCurrentFrame)
|
||
|
{
|
||
|
runSynchronouslyOnVideoProcessingQueue(^
|
||
|
{
|
||
|
[GPUImageContext useImageProcessingContext];
|
||
|
[self _render];
|
||
|
|
||
|
if ([GPUImageContext supportsFastTextureUpload])
|
||
|
{
|
||
|
glFinish();
|
||
|
_rawBytesForImage = [_outputFramebuffer byteBuffer];
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
glReadPixels(0, 0, (GLsizei)_imageSize.width, (GLsizei)_imageSize.height, GL_RGBA, GL_UNSIGNED_BYTE, _rawBytesForImage);
|
||
|
}
|
||
|
|
||
|
_hasReadCurrentFrame = true;
|
||
|
});
|
||
|
}
|
||
|
|
||
|
return _rawBytesForImage;
|
||
|
}
|
||
|
|
||
|
@end
|