1
0
mirror of https://github.com/danog/Telegram.git synced 2024-12-02 17:38:07 +01:00
Telegram/thirdparty/PGPhotoEditor/PGPhotoEnhanceLUTGenerator.m

333 lines
10 KiB
Mathematica
Raw Normal View History

2015-10-01 18:19:52 +02:00
#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