#import "TGImageBlur.h"

#import "LegacyComponentsInternal.h"
#import "LegacyComponentsGlobals.h"
#import "TGImageUtils.h"

#import <Accelerate/Accelerate.h>

#import "UIImage+TG.h"
#import "TGStaticBackdropImageData.h"
#import "TGStaticBackdropAreaData.h"

static inline uint64_t get_colors (const uint8_t *p) {
    return p[0] + (p[1] << 16) + ((uint64_t)p[2] << 32);
}

static void fastBlur (int imageWidth, int imageHeight, int imageStride, void *pixels)
{
    uint8_t *pix = (uint8_t *)pixels;
    const int w = imageWidth;
    const int h = imageHeight;
    const int stride = imageStride;
    const int radius = 3;
    const int r1 = radius + 1;
    const int div = radius * 2 + 1;
    
    if (radius > 15 || div >= w || div >= h)
    {
        return;
    }
    
    uint64_t rgb[imageStride * imageHeight];
    
    int x, y, i;
    
    int yw = 0;
    const int we = w - r1;
    for (y = 0; y < h; y++) {
        uint64_t cur = get_colors (&pix[yw]);
        uint64_t rgballsum = -radius * cur;
        uint64_t rgbsum = cur * ((r1 * (r1 + 1)) >> 1);
        
        for (i = 1; i <= radius; i++) {
            uint64_t cur = get_colors (&pix[yw + i * 4]);
            rgbsum += cur * (r1 - i);
            rgballsum += cur;
        }
        
        x = 0;
        
#define update(start, middle, end)                         \
rgb[y * w + x] = (rgbsum >> 4) & 0x00FF00FF00FF00FF; \
\
rgballsum += get_colors (&pix[yw + (start) * 4]) -   \
2 * get_colors (&pix[yw + (middle) * 4]) +  \
get_colors (&pix[yw + (end) * 4]);      \
rgbsum += rgballsum;                                 \
x++;                                                 \

        while (x < r1) {
            update (0, x, x + r1);
        }
        while (x < we) {
            update (x - r1, x, x + r1);
        }
        while (x < w) {
            update (x - r1, x, w - 1);
        }
#undef update
        
        yw += stride;
    }
    
    const int he = h - r1;
    for (x = 0; x < w; x++) {
        uint64_t rgballsum = -radius * rgb[x];
        uint64_t rgbsum = rgb[x] * ((r1 * (r1 + 1)) >> 1);
        for (i = 1; i <= radius; i++) {
            rgbsum += rgb[i * w + x] * (r1 - i);
            rgballsum += rgb[i * w + x];
        }
        
        y = 0;
        int yi = x * 4;
        
#define update(start, middle, end)         \
int64_t res = rgbsum >> 4;           \
pix[yi] = (uint8_t)res;                       \
pix[yi + 1] = (uint8_t)(res >> 16);             \
pix[yi + 2] = (uint8_t)(res >> 32);             \
\
rgballsum += rgb[x + (start) * w] -  \
2 * rgb[x + (middle) * w] + \
rgb[x + (end) * w];     \
rgbsum += rgballsum;                 \
y++;                                 \
yi += stride;
        
        while (y < r1) {
            update (0, y, y + r1);
        }
        while (y < he) {
            update (y - r1, y, y + r1);
        }
        while (y < h) {
            update (y - r1, y, h - 1);
        }
#undef update
    }
}

static void fastBlurMore (int imageWidth, int imageHeight, int imageStride, void *pixels)
{
    uint8_t *pix = (uint8_t *)pixels;
    const int w = imageWidth;
    const int h = imageHeight;
    const int stride = imageStride;
    const int radius = 7;
    const int r1 = radius + 1;
    const int div = radius * 2 + 1;
    
    if (radius > 15 || div >= w || div >= h)
    {
        return;
    }
    
    uint64_t *rgb = malloc(imageStride * imageHeight * sizeof(uint64_t));
    
    int x, y, i;
    
    int yw = 0;
    const int we = w - r1;
    for (y = 0; y < h; y++) {
        uint64_t cur = get_colors (&pix[yw]);
        uint64_t rgballsum = -radius * cur;
        uint64_t rgbsum = cur * ((r1 * (r1 + 1)) >> 1);
        
        for (i = 1; i <= radius; i++) {
            uint64_t cur = get_colors (&pix[yw + i * 4]);
            rgbsum += cur * (r1 - i);
            rgballsum += cur;
        }
        
        x = 0;
        
#define update(start, middle, end)                         \
rgb[y * w + x] = (rgbsum >> 6) & 0x00FF00FF00FF00FF; \
\
rgballsum += get_colors (&pix[yw + (start) * 4]) -   \
2 * get_colors (&pix[yw + (middle) * 4]) +  \
get_colors (&pix[yw + (end) * 4]);      \
rgbsum += rgballsum;                                 \
x++;                                                 \

        while (x < r1) {
            update (0, x, x + r1);
        }
        while (x < we) {
            update (x - r1, x, x + r1);
        }
        while (x < w) {
            update (x - r1, x, w - 1);
        }
#undef update
        
        yw += stride;
    }
    
    const int he = h - r1;
    for (x = 0; x < w; x++) {
        uint64_t rgballsum = -radius * rgb[x];
        uint64_t rgbsum = rgb[x] * ((r1 * (r1 + 1)) >> 1);
        for (i = 1; i <= radius; i++) {
            rgbsum += rgb[i * w + x] * (r1 - i);
            rgballsum += rgb[i * w + x];
        }
        
        y = 0;
        int yi = x * 4;
        
#define update(start, middle, end)         \
int64_t res = rgbsum >> 6;           \
pix[yi] = (uint8_t)res;                       \
pix[yi + 1] = (uint8_t)(res >> 16);             \
pix[yi + 2] = (uint8_t)(res >> 32);             \
\
rgballsum += rgb[x + (start) * w] -  \
2 * rgb[x + (middle) * w] + \
rgb[x + (end) * w];     \
rgbsum += rgballsum;                 \
y++;                                 \
yi += stride;
        
        while (y < r1) {
            update (0, y, y + r1);
        }
        while (y < he) {
            update (y - r1, y, y + r1);
        }
        while (y < h) {
            update (y - r1, y, h - 1);
        }
#undef update
    }
    
    free(rgb);
}

static void matrixMul(CGFloat *a, CGFloat *b, CGFloat *result)
{
	for (int i = 0; i != 4; ++i)
	{
 		for (int j = 0; j != 4; ++j)
		{
			CGFloat sum = 0;
			for (int k = 0; k != 4; ++k)
			{
				sum += a[i + k * 4] * b[k + j * 4];
			}
			result[i + j * 4] = sum;
		}
	}
}

static inline CGSize fitSize(CGSize size, CGSize maxSize)
{
    if (size.width < 1)
        size.width = 1;
    if (size.height < 1)
        size.height = 1;
    
    if (size.width > maxSize.width)
    {
        size.height = CGFloor((size.height * maxSize.width / size.width));
        size.width = maxSize.width;
    }
    if (size.height > maxSize.height)
    {
        size.width = CGFloor((size.width * maxSize.height / size.height));
        size.height = maxSize.height;
    }
    return size;
}

static void computeImageVariance(uint8_t *memory, int width, int height, int stride, float *outVariance, float *outLuminance, float *outRealLuminance)
{
    uint32_t rnSum = 0;
    uint32_t gnSum = 0;
    uint32_t bnSum = 0;
    
    uint64_t rnSumSq = 0;
    uint64_t gnSumSq = 0;
    uint64_t bnSumSq = 0;
    
    uint32_t luminanceSum = 0;
    //uint64_t luminanceSumSq = 0;
    
    /*float rSum = 0.0f;
    float gSum = 0.0f;
    float bSum = 0.0f;
    
    float rSumSq = 0.0f;
    float gSumSq = 0.0f;
    float bSumSq = 0.0f;*/
    
    uint32_t histogram[10] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
    
    for (int y = 0; y < height; y++)
    {
        for (int x = 0; x < width; x++)
        {
            uint32_t color = *((uint32_t *)&memory[y * stride + x * 4]);
            
            uint32_t r = (color >> 16) & 0xff;
            uint32_t g = (color >> 8) & 0xff;
            uint32_t b = color & 0xff;
            
            uint32_t pixelLuminance = (uint8_t)((r * 299 + g * 587 + b * 114) / 1000);
            histogram[(pixelLuminance * 9 / 255) % 10]++;
            
            luminanceSum += pixelLuminance;
            //luminanceSumSq += pixelLuminance * pixelLuminance;
            
            rnSum += r;
            gnSum += g;
            bnSum += b;
            
            rnSumSq += r * r;
            gnSumSq += g * g;
            bnSumSq += b * b;
            
            /*rSum += r / 255.0f;
            gSum += g / 255.0f;
            bSum += b / 255.0f;
            
            rSumSq += (r / 255.0f) * (r / 255.0f);
            gSumSq += (g / 255.0f) * (g / 255.0f);
            bSumSq += (b / 255.0f) * (b / 255.0f);*/
        }
    }
    
    int n = width * height;
    
    /*float rVariance = (rSumSq - (rSum * rSum) / n) / (n);
    float gVariance = (gSumSq - (gSum * gSum) / n) / (n);
    float bVariance = (bSumSq - (bSum * bSum) / n) / (n);*/
    
    float rnVariance = ((uint64_t)((rnSumSq / 255) - ((uint64_t)rnSum * (uint64_t)rnSum / 255) / n)) / (255.0f * n);
    float gnVariance = ((uint64_t)((gnSumSq / 255) - ((uint64_t)gnSum * (uint64_t)gnSum / 255) / n)) / (255.0f * n);
    float bnVariance = ((uint64_t)((bnSumSq / 255) - ((uint64_t)bnSum * (uint64_t)bnSum / 255) / n)) / (255.0f * n);
    
    float variance = rnVariance + gnVariance + bnVariance;
    if (outVariance != NULL)
        *outVariance = variance;
    
    //float luminanceVariance = ((uint64_t)((luminanceSumSq / 255) - ((uint64_t)luminanceSum * (uint64_t)luminanceSum / 255) / n)) / (255.0f * n);
    
    float floatHistogram[10];
    
    float norm = (float)(width * height);
    
    float n0 = 0.0f;
    float n1 = 0.0f;
    for (int i = 0; i < 10; i++)
    {
        floatHistogram[i] = histogram[i] / norm;
        
        if (i <= 6)
            n0 += floatHistogram[i];
        else
            n1 += floatHistogram[i];
    }
    
    //TGLegacyLog(@"histogram: [%f %f %f %f %f %f %f %f %f %f]", floatHistogram[0], floatHistogram[1], floatHistogram[2], floatHistogram[3], floatHistogram[4], floatHistogram[5], floatHistogram[6], floatHistogram[7], floatHistogram[8], floatHistogram[9]);
    
    if (outLuminance != NULL)
        *outLuminance = n0 < n1 ? 0.95f : 0.5f;
    
    if (outRealLuminance != NULL)
        *outRealLuminance = (luminanceSum / (norm * 255.0f));
}

static void fastScaleImage(uint8_t *sourceMemory, int sourceWidth, int sourceHeight, int sourceStride, uint8_t *targetMemory, int targetWidth, int targetHeight, int targetStride, CGRect sourceRectInTargetSpace)
{
    int imageX = MIN(0, (int)sourceRectInTargetSpace.origin.x);
    int imageY = MIN(0, (int)sourceRectInTargetSpace.origin.y);
    int imageWidth = (int)sourceRectInTargetSpace.size.width;
    int imageHeight = (int)sourceRectInTargetSpace.size.height;
    
    for (int y = 0; y < targetHeight; y++)
    {
        for (int x = 0; x < targetWidth; x++)
        {
            int sourceY = (y - imageY) * sourceHeight / imageHeight;
            int sourceX = (x - imageX) * sourceWidth / imageWidth;
            
            if (sourceX >= 0 && sourceY >= 0 && sourceX < sourceWidth && sourceY < sourceHeight)
            {
                uint32_t color = *((uint32_t *)&sourceMemory[sourceY * sourceStride + sourceX * 4]);
                *((uint32_t *)&targetMemory[y * targetStride + x * 4]) = color;
            }
        }
    }
}

static uint32_t TGImageAverageColor(void *memory, const unsigned int width, const unsigned int height, const unsigned int stride)
{
    int32_t av0 = 0;
    int32_t av1 = 0;
    int32_t av2 = 0;
    
    for (unsigned int y = 0; y < height; y++)
    {
        for (unsigned int x = 0; x < width; x++)
        {
            uint32_t pixel = *((uint32_t *)(&memory[y * stride + x * 4]));
            av0 += pixel & 0xff;
            av1 += (pixel >> 8) & 0xff;
            av2 += (pixel >> 16) & 0xff;
        }
    }
    
    uint32_t norm = (width * height);
    av0 = av0 / norm;
    av1 = av1 / norm;
    av2 = av2 / norm;
    
    return 0xff000000 | av0 | (av1 << 8) | (av2 << 16);
}

static inline uint32_t alphaComposePremultipliedPixels(uint32_t a, uint32_t b)
{
    uint32_t a0 = ((a >> 24) & 0xff);
    uint32_t a1 = ((b >> 24) & 0xff);
    
    uint32_t r0 = (a >> 16) & 0xff;
    uint32_t g0 = (a >> 8) & 0xff;
    uint32_t b0 = a & 0xff;
    
    uint32_t r1 = (b >> 16) & 0xff;
    uint32_t g1 = (b >> 8) & 0xff;
    uint32_t b1 = b & 0xff;
    
    uint32_t ta = ((a0 * a0) >> 8) + ((a1 * (255 - ((a0 * a0) >> 8))) >> 8);
    uint32_t tr = ((r0 * a0) >> 8) + ((r1 * (255 - ((a0 * a0) >> 8))) >> 8);
    uint32_t tg = ((g0 * a0) >> 8) + ((g1 * (255 - ((a0 * a0) >> 8))) >> 8);
    uint32_t tb = ((b0 * a0) >> 8) + ((b1 * (255 - ((a0 * a0) >> 8))) >> 8);
    
    return (ta << 24) | (tr << 16) | (tg << 8) | tb;
}

static inline uint32_t premultipliedPixel(uint32_t rgb, uint32_t alpha)
{
    uint32_t r = (((rgb >> 16) & 0xff) * alpha) >> 8;
    uint32_t g = (((rgb >> 8) & 0xff) * alpha) >> 8;
    uint32_t b = ((rgb & 0xff) * alpha) >> 8;
    
    return (alpha << 24) | (r << 16) | (g << 8) | b;
}

typedef enum {
    TGAttachmentPositionNone = 0,
    TGAttachmentPositionTop = 1 << 0,
    TGAttachmentPositionBottom = 1 << 1,
    TGAttachmentPositionLeft = 1 << 2,
    TGAttachmentPositionRight = 1 << 3,
    TGAttachmentPositionInside = 1 << 4
} TGAttachmentPosition;

static void addAttachmentImageCorners(void *memory, const unsigned int width, const unsigned int height, const unsigned int stride, int position, float fract)
{
    const int scale = (int)TGScreenScaling();
    
    const int shadowSize = 1;
    const int strokeWidth = scale;
    const int smallRadius = floor(3 * scale * fract);
    const int bigRadius = floor(16 * scale * fract);
    
    int topLeftRadius = smallRadius;
    int topRightRadius = smallRadius;
    int bottomLeftRadius = smallRadius;
    int bottomRightRadius = smallRadius;
    
    if (position == TGAttachmentPositionNone)
        topLeftRadius = topRightRadius = bottomLeftRadius = bottomRightRadius = bigRadius;
    else if (position == TGAttachmentPositionInside)
        topLeftRadius = topRightRadius = bottomLeftRadius = bottomRightRadius = smallRadius;
    
    if (position & TGAttachmentPositionTop && position & TGAttachmentPositionLeft)
        topLeftRadius = bigRadius;
    if (position & TGAttachmentPositionTop && position & TGAttachmentPositionRight)
        topRightRadius = bigRadius;
    if (position & TGAttachmentPositionBottom && position & TGAttachmentPositionLeft)
        bottomLeftRadius = bigRadius;
    if (position & TGAttachmentPositionBottom && position & TGAttachmentPositionRight)
        bottomRightRadius = bigRadius;
    
    const int contextWidth = MAX(topLeftRadius, bottomLeftRadius) + MAX(topRightRadius, bottomRightRadius) + shadowSize * 2 + strokeWidth * 2;
    const int contextHeight = MAX(topLeftRadius, topRightRadius) + MAX(bottomLeftRadius, bottomRightRadius) + shadowSize * 2 + strokeWidth * 2;
    const int contextStride = (4 * contextWidth + 15) & (~15);
    
    uint32_t shadowColorRaw = 0x6b86a9c9;
    uint32_t strokeColorArgb = 0xffffffff;
    
    TGImageBorderPallete *pallete = nil;
    if ([[LegacyComponentsGlobals provider] respondsToSelector:@selector(imageBorderPallete)])
        pallete = [[LegacyComponentsGlobals provider] imageBorderPallete];
    
    if (pallete != nil)
    {
        uint32_t strokeColorRaw = TGColorHexCodeWithAlpha(pallete.borderColor);
        strokeColorArgb = premultipliedPixel(strokeColorRaw & 0xffffff, ((strokeColorRaw >> 24) & 0xff));
        shadowColorRaw = TGColorHexCodeWithAlpha(pallete.shadowColor);
    }
    const uint32_t shadowColorArgb = premultipliedPixel(shadowColorRaw & 0xffffff, MAX(0, ((shadowColorRaw >> 24) & 0xff)));
    
    static uint8_t *defaultContextMemory = NULL;
    static uint8_t *defaultAlphaMemory = NULL;
    
    uint8_t *contextMemory = NULL;
    uint8_t *alphaMemory = NULL;

    static uint32_t cachedColor = UINT32_MAX;
    if (position == TGAttachmentPositionNone && (cachedColor == UINT32_MAX || cachedColor == strokeColorArgb))
    {
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^
        {
            defaultContextMemory = malloc(contextStride * contextHeight);
            memset(defaultContextMemory, 0, contextStride * contextHeight);
            
            defaultAlphaMemory = malloc(contextStride * contextHeight);
            
            CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
            CGBitmapInfo bitmapInfo = kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host;
            CGContextRef targetContext = CGBitmapContextCreate(defaultContextMemory, contextWidth, contextHeight, 8, contextStride, colorSpace, bitmapInfo);
            CFRelease(colorSpace);
            
            CGContextSetFillColorWithColor(targetContext, [UIColor blackColor].CGColor);
            CGContextFillEllipseInRect(targetContext, CGRectMake(shadowSize + strokeWidth / 2.0f, shadowSize + strokeWidth / 2.0f, contextWidth - (shadowSize + strokeWidth / 2.0f) * 2.0f, contextHeight - (shadowSize + strokeWidth / 2.0f) * 2.0f));
            
            memcpy(defaultAlphaMemory, defaultContextMemory, contextStride * contextHeight);
            
            memset(defaultContextMemory, 0, contextStride * contextHeight);
            
            CGContextSetStrokeColorWithColor(targetContext, UIColorRGBA(shadowColorRaw, ((shadowColorRaw >> 24) & 0xff) / 255.0f).CGColor);
            CGContextSetLineWidth(targetContext, shadowSize);
            CGContextStrokeEllipseInRect(targetContext, CGRectMake(shadowSize / 2.0f, shadowSize / 2.0f, contextWidth - shadowSize, contextHeight - shadowSize));
            CGContextStrokeEllipseInRect(targetContext, CGRectMake(shadowSize / 2.0f + 0.5f, shadowSize / 2.0f - 0.5f, contextWidth - shadowSize, contextHeight - shadowSize));
            
            CGContextSetStrokeColorWithColor(targetContext, UIColorRGBA(shadowColorRaw, (((shadowColorRaw >> 24) & 0xff) / 255.0f) * 0.5f).CGColor);
            CGContextStrokeEllipseInRect(targetContext, CGRectMake(shadowSize / 2.0f - 0.2f, shadowSize / 2.0f + 0.2f, contextWidth - shadowSize, contextHeight - shadowSize));
            
            CGContextSetStrokeColorWithColor(targetContext, UIColorRGB(strokeColorArgb).CGColor);
            CGContextSetLineWidth(targetContext, strokeWidth);
            CGContextStrokeEllipseInRect(targetContext, CGRectMake(shadowSize + strokeWidth / 2.0f, shadowSize + strokeWidth / 2.0f, contextWidth - (shadowSize + strokeWidth / 2.0f) * 2.0f, contextHeight - (shadowSize + strokeWidth / 2.0f) * 2.0f));
            
            CGContextSetStrokeColorWithColor(targetContext, UIColorRGBA(strokeColorArgb, 0.4f).CGColor);
            CGContextStrokeEllipseInRect(targetContext, CGRectMake(shadowSize + strokeWidth / 2.0f + 0.5f, shadowSize + strokeWidth / 2.0f - 0.5f, contextWidth - (shadowSize + strokeWidth / 2.0f) * 2.0f, contextHeight - (shadowSize + strokeWidth / 2.0f) * 2.0f));
            
            CFRelease(targetContext);
            
            cachedColor = strokeColorArgb;
        });
        
        contextMemory = defaultContextMemory;
        alphaMemory = defaultAlphaMemory;
    }
    else
    {
        contextMemory = malloc(contextStride * contextHeight);
        memset(contextMemory, 0, contextStride * contextHeight);
        
        alphaMemory = malloc(contextStride * contextHeight);
        
        CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
        CGBitmapInfo bitmapInfo = kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host;
        CGContextRef targetContext = CGBitmapContextCreate(contextMemory, contextWidth, contextHeight, 8, contextStride, colorSpace, bitmapInfo);
        CFRelease(colorSpace);
        
        void (^drawPie)(CGPoint, CGFloat, CGFloat, CGFloat, bool) = ^(CGPoint center, CGFloat radius, CGFloat start, CGFloat end, bool fill)
        {
            if (fill)
                CGContextMoveToPoint(targetContext, center.x, contextHeight - center.y);
            CGContextAddArc(targetContext, center.x, contextHeight - center.y, radius, start, end, false);
            
            if (fill)
            {
                CGContextFillPath(targetContext);
            }
            else
            {
                CGContextReplacePathWithStrokedPath(targetContext);
                CGContextFillPath(targetContext);
            }
        };
        
        CGFloat (^calcRadius)(CGFloat, CGFloat) = ^CGFloat(CGFloat initialRadius, CGFloat offset)
        {
            return initialRadius + shadowSize + strokeWidth - offset;
        };
        
        void (^draw)(CGFloat, bool) = ^(CGFloat offset, bool fill)
        {
            CGFloat radius = calcRadius(topLeftRadius, offset);
            CGPoint point = CGPointMake(offset + radius, offset + radius);
            drawPie(point, radius, M_PI_2, M_PI, fill);
            
            radius = calcRadius(topRightRadius, offset);
            point = CGPointMake(contextWidth - offset - radius, offset + radius);
            drawPie(point, radius, 0, M_PI_2, fill);

            radius = calcRadius(bottomLeftRadius, offset);
            point = CGPointMake(offset + radius, contextHeight - offset - radius);
            drawPie(point, radius, M_PI, 3 * M_PI_2, fill);

            radius = calcRadius(bottomRightRadius, offset);
            point = CGPointMake(contextWidth - offset - radius, contextHeight - offset - radius);
            drawPie(point, radius, 3 * M_PI_2, 2 * M_PI, fill);
        };
        
        CGContextSetFillColorWithColor(targetContext, [UIColor blackColor].CGColor);
        draw(shadowSize + strokeWidth / 2.0, true);
        
        memcpy(alphaMemory, contextMemory, contextStride * contextHeight);
        
        memset(contextMemory, 0, contextStride * contextHeight);

        CGContextSetFillColorWithColor(targetContext, UIColorRGBA(shadowColorRaw, ((shadowColorRaw >> 24) & 0xff) / 255.0f).CGColor);
        CGContextSetLineWidth(targetContext, shadowSize);
        draw(shadowSize / 2.0f, false);
        draw(shadowSize / 2.0f + 0.5f, false);

        CGContextSetFillColorWithColor(targetContext, UIColorRGBA(shadowColorRaw, (((shadowColorRaw >> 24) & 0xff) / 255.0f) * 0.5f).CGColor);
        draw(shadowSize / 2.0f, false);

        CGContextSetFillColorWithColor(targetContext, UIColorRGB(strokeColorArgb).CGColor);
        CGContextSetLineWidth(targetContext, strokeWidth);
        draw(shadowSize + strokeWidth / 2.0f, false);

        CGContextSetFillColorWithColor(targetContext, UIColorRGBA(strokeColorArgb, 0.4f).CGColor);
        draw(shadowSize + strokeWidth / 2.0f + 0.5f, false);
        
        CFRelease(targetContext);
    }
    
    const unsigned int topLeftRadiusWithPadding = topLeftRadius + shadowSize + strokeWidth;
    for (unsigned int y = 0; y < topLeftRadiusWithPadding; y++)
    {
        for (unsigned int x = 0; x < topLeftRadiusWithPadding; x++)
        {
            uint32_t alpha = alphaMemory[y * contextStride + x * 4 + 3];
            uint32_t pixel = *((uint32_t *)(&memory[y * stride + x * 4]));
            
            pixel = (alpha << 24) | (((((pixel >> 16) & 0xff) * alpha) >> 8) << 16) | (((((pixel >> 8) & 0xff) * alpha) >> 8) << 8) | (((((pixel >> 0) & 0xff) * alpha) >> 8) << 0);
            pixel = alphaComposePremultipliedPixels(*((uint32_t *)&contextMemory[y * contextStride + x * 4]), pixel);
            *((uint32_t *)(&memory[y * stride + x * 4])) = pixel;
        }
    }
    
    const unsigned int topRightRadiusWithPadding = topRightRadius + shadowSize + strokeWidth;
    const unsigned int topRightRadiusOrigin = width - topRightRadiusWithPadding;
    for (int y = 0; y < shadowSize; y++)
    {
        for (unsigned int x = topLeftRadiusWithPadding; x < topRightRadiusOrigin; x++)
        {
            *((uint32_t *)(&memory[y * stride + x * 4])) = shadowColorArgb;
        }
    }
    
    for (int y = shadowSize; y < shadowSize + strokeWidth; y++)
    {
        for (unsigned int x = topLeftRadiusWithPadding; x < topRightRadiusOrigin; x++)
        {
            *((uint32_t *)(&memory[y * stride + x * 4])) = strokeColorArgb;
        }
    }
    
    for (unsigned int y = 0; y < topRightRadiusWithPadding; y++)
    {
        for (unsigned int x = topRightRadiusOrigin; x < width; x++)
        {
            uint32_t alpha = alphaMemory[y * contextStride + ((contextWidth - topRightRadiusWithPadding) + (x - topRightRadiusOrigin)) * 4 + 3];
            uint32_t pixel = *((uint32_t *)(&memory[y * stride + x * 4]));

            pixel = (alpha << 24) | (((((pixel >> 16) & 0xff) * alpha) >> 8) << 16) | (((((pixel >> 8) & 0xff) * alpha) >> 8) << 8) | (((((pixel >> 0) & 0xff) * alpha) >> 8) << 0);
            pixel = alphaComposePremultipliedPixels(*((uint32_t *)&contextMemory[y * contextStride + ((contextWidth - topRightRadiusWithPadding) + (x - topRightRadiusOrigin)) * 4]), pixel);
            *((uint32_t *)(&memory[y * stride + x * 4])) = pixel;
        }
    }
    
    const unsigned int bottomLeftRadiusWithPadding = bottomLeftRadius + shadowSize + strokeWidth;
    const unsigned int bottomLeftRadiusOriginY = height - bottomLeftRadiusWithPadding;
    for (unsigned int y = bottomLeftRadiusOriginY; y < height; y++)
    {
        for (unsigned int x = 0; x < bottomLeftRadiusWithPadding; x++)
        {
            uint32_t alpha = alphaMemory[((contextHeight - bottomLeftRadiusWithPadding) + (y - bottomLeftRadiusOriginY)) * contextStride + x * 4 + 3];
            uint32_t pixel = *((uint32_t *)(&memory[y * stride + x * 4]));
            
            pixel = (alpha << 24) | (((((pixel >> 16) & 0xff) * alpha) >> 8) << 16) | (((((pixel >> 8) & 0xff) * alpha) >> 8) << 8) | (((((pixel >> 0) & 0xff) * alpha) >> 8) << 0);
            pixel = alphaComposePremultipliedPixels(*((uint32_t *)&contextMemory[((contextHeight - bottomLeftRadiusWithPadding) + (y - bottomLeftRadiusOriginY)) * contextStride + x * 4]), pixel);
            *((uint32_t *)(&memory[y * stride + x * 4])) = pixel;
        }
    }
    
    const unsigned int bottomRightRadiusWithPadding = bottomRightRadius + shadowSize + strokeWidth;
    const unsigned int bottomRightRadiusOriginX = width - bottomRightRadiusWithPadding;
    const unsigned int bottomRightRadiusOriginY = height - bottomRightRadiusWithPadding;
    
    for (unsigned int y = topLeftRadiusWithPadding; y < height - bottomLeftRadiusWithPadding; y++)
    {
        for (int x = 0; x < shadowSize; x++)
        {
            *((uint32_t *)(&memory[y * stride + x * 4])) = shadowColorArgb;
        }
        
        for (int x = shadowSize; x < shadowSize + strokeWidth; x++)
        {
            *((uint32_t *)(&memory[y * stride + x * 4])) = strokeColorArgb;
        }
    }
    
    for (unsigned int y = topRightRadiusWithPadding; y < height - bottomRightRadiusWithPadding; y++)
    {
        for (unsigned int x = width - shadowSize - strokeWidth; x < width - shadowSize; x++)
        {
            *((uint32_t *)(&memory[y * stride + x * 4])) = strokeColorArgb;
        }
        
        for (unsigned int x = width - shadowSize; x < width; x++)
        {
            *((uint32_t *)(&memory[y * stride + x * 4])) = shadowColorArgb;
        }
    }
    
    for (unsigned int y = height - shadowSize - strokeWidth; y < height - shadowSize; y++)
    {
        for (unsigned int x = bottomLeftRadiusWithPadding; x < bottomRightRadiusOriginX; x++)
        {
            *((uint32_t *)(&memory[y * stride + x * 4])) = strokeColorArgb;
        }
    }
    
    for (unsigned int y = height - shadowSize; y < height; y++)
    {
        for (unsigned int x = bottomLeftRadiusWithPadding; x < bottomRightRadiusOriginX; x++)
        {
            *((uint32_t *)(&memory[y * stride + x * 4])) = shadowColorArgb;
        }
    }
    
    for (unsigned int y = bottomRightRadiusOriginY; y < height; y++)
    {
        for (unsigned int x = bottomRightRadiusOriginX; x < width; x++)
        {
            uint32_t alpha = alphaMemory[((contextHeight - bottomRightRadiusWithPadding) + (y - bottomRightRadiusOriginY)) * contextStride + ((contextWidth - bottomRightRadiusWithPadding) + (x - bottomRightRadiusOriginX)) * 4 + 3];
            uint32_t pixel = *((uint32_t *)(&memory[y * stride + x * 4]));

            pixel = (alpha << 24) | (((((pixel >> 16) & 0xff) * alpha) >> 8) << 16) | (((((pixel >> 8) & 0xff) * alpha) >> 8) << 8) | (((((pixel >> 0) & 0xff) * alpha) >> 8) << 0);
            pixel = alphaComposePremultipliedPixels(*((uint32_t *)&contextMemory[((contextHeight - bottomRightRadiusWithPadding) + (y - bottomRightRadiusOriginY)) * contextStride + ((contextWidth - bottomRightRadiusWithPadding) + (x - bottomRightRadiusOriginX)) * 4]), pixel);
            *((uint32_t *)(&memory[y * stride + x * 4])) = pixel;
        }
    }
    
    if (contextMemory != defaultContextMemory)
    {
        free(contextMemory);
        free(alphaMemory);
    }
}

void TGAddImageCorners(void *memory, const unsigned int width, const unsigned int height, const unsigned int stride, int radius, int position)
{
    const int scale = TGScreenScaling();
    
    const int smallRadius = 3 * scale;
    const int bigRadius = radius * scale;
    
    int topLeftRadius = smallRadius;
    int topRightRadius = smallRadius;
    int bottomLeftRadius = smallRadius;
    int bottomRightRadius = smallRadius;
    
    if (position == TGAttachmentPositionNone)
        topLeftRadius = topRightRadius = bottomLeftRadius = bottomRightRadius = bigRadius;
    else if (position == TGAttachmentPositionInside)
        topLeftRadius = topRightRadius = bottomLeftRadius = bottomRightRadius = smallRadius;
    
    if (position & TGAttachmentPositionTop && position & TGAttachmentPositionLeft)
        topLeftRadius = bigRadius;
    if (position & TGAttachmentPositionTop && position & TGAttachmentPositionRight)
        topRightRadius = bigRadius;
    if (position & TGAttachmentPositionBottom && position & TGAttachmentPositionLeft)
        bottomLeftRadius = bigRadius;
    if (position & TGAttachmentPositionBottom && position & TGAttachmentPositionRight)
        bottomRightRadius = bigRadius;
    
    const int contextWidth = MAX(topLeftRadius, bottomLeftRadius) + MAX(topRightRadius, bottomRightRadius);
    const int contextHeight = MAX(topLeftRadius, topRightRadius) + MAX(bottomLeftRadius, bottomRightRadius);
    const int contextStride = (4 * contextWidth + 15) & (~15);
    
    if (radius <= 0 || contextWidth > width || contextHeight > height) {
        return;
    }
    
    uint8_t *contextMemory = NULL;
    uint8_t *alphaMemory = NULL;
    
    {        
        contextMemory = malloc(contextStride * contextHeight);
        memset(contextMemory, 0, contextStride * contextHeight);
        
        alphaMemory = malloc(contextStride * contextHeight);
        
        CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
        CGBitmapInfo bitmapInfo = kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host;
        CGContextRef targetContext = CGBitmapContextCreate(contextMemory, contextWidth, contextHeight, 8, contextStride, colorSpace, bitmapInfo);
        CFRelease(colorSpace);
        
        void (^drawPie)(CGPoint, CGFloat, CGFloat, CGFloat, bool) = ^(CGPoint center, CGFloat radius, CGFloat start, CGFloat end, bool fill)
        {
            if (fill)
                CGContextMoveToPoint(targetContext, center.x, contextHeight - center.y);
            CGContextAddArc(targetContext, center.x, contextHeight - center.y, radius, start, end, false);
            
            if (fill)
            {
                CGContextFillPath(targetContext);
            }
            else
            {
                CGContextReplacePathWithStrokedPath(targetContext);
                CGContextFillPath(targetContext);
            }
        };
        
        CGFloat (^calcRadius)(CGFloat, CGFloat) = ^CGFloat(CGFloat initialRadius, CGFloat offset)
        {
            return initialRadius - offset;
        };
        
        void (^draw)(CGFloat, bool) = ^(CGFloat offset, bool fill)
        {
            CGFloat radius = calcRadius(topLeftRadius, offset);
            CGPoint point = CGPointMake(offset + radius, offset + radius);
            drawPie(point, radius, M_PI_2, M_PI, fill);
            
            radius = calcRadius(topRightRadius, offset);
            point = CGPointMake(contextWidth - offset - radius, offset + radius);
            drawPie(point, radius, 0, M_PI_2, fill);
            
            radius = calcRadius(bottomLeftRadius, offset);
            point = CGPointMake(offset + radius, contextHeight - offset - radius);
            drawPie(point, radius, M_PI, 3 * M_PI_2, fill);
            
            radius = calcRadius(bottomRightRadius, offset);
            point = CGPointMake(contextWidth - offset - radius, contextHeight - offset - radius);
            drawPie(point, radius, 3 * M_PI_2, 2 * M_PI, fill);
        };
        
        CGContextSetFillColorWithColor(targetContext, [UIColor blackColor].CGColor);
        draw(0.0f, true);
        
        memcpy(alphaMemory, contextMemory, contextStride * contextHeight);
        
        memset(contextMemory, 0, contextStride * contextHeight);

        CFRelease(targetContext);
    }
    
    const unsigned int topLeftRadiusWithPadding = topLeftRadius;
    for (unsigned int y = 0; y < topLeftRadiusWithPadding; y++)
    {
        for (unsigned int x = 0; x < topLeftRadiusWithPadding; x++)
        {
            uint32_t alpha = alphaMemory[y * contextStride + x * 4 + 3];
            uint32_t pixel = *((uint32_t *)(&memory[y * stride + x * 4]));
            
            pixel = (alpha << 24) | (((((pixel >> 16) & 0xff) * alpha) >> 8) << 16) | (((((pixel >> 8) & 0xff) * alpha) >> 8) << 8) | (((((pixel >> 0) & 0xff) * alpha) >> 8) << 0);
            pixel = alphaComposePremultipliedPixels(*((uint32_t *)&contextMemory[y * contextStride + x * 4]), pixel);
            *((uint32_t *)(&memory[y * stride + x * 4])) = pixel;
        }
    }
    
    const unsigned int topRightRadiusWithPadding = topRightRadius;
    const unsigned int topRightRadiusOrigin = width - topRightRadiusWithPadding;
    
    for (unsigned int y = 0; y < topRightRadiusWithPadding; y++)
    {
        for (unsigned int x = topRightRadiusOrigin; x < width; x++)
        {
            uint32_t alpha = alphaMemory[y * contextStride + ((contextWidth - topRightRadiusWithPadding) + (x - topRightRadiusOrigin)) * 4 + 3];
            uint32_t pixel = *((uint32_t *)(&memory[y * stride + x * 4]));
            
            pixel = (alpha << 24) | (((((pixel >> 16) & 0xff) * alpha) >> 8) << 16) | (((((pixel >> 8) & 0xff) * alpha) >> 8) << 8) | (((((pixel >> 0) & 0xff) * alpha) >> 8) << 0);
            pixel = alphaComposePremultipliedPixels(*((uint32_t *)&contextMemory[y * contextStride + ((contextWidth - topRightRadiusWithPadding) + (x - topRightRadiusOrigin)) * 4]), pixel);
            *((uint32_t *)(&memory[y * stride + x * 4])) = pixel;
        }
    }
    
    const unsigned int bottomLeftRadiusWithPadding = bottomLeftRadius;
    const unsigned int bottomLeftRadiusOriginY = height - bottomLeftRadiusWithPadding;
    for (unsigned int y = bottomLeftRadiusOriginY; y < height; y++)
    {
        for (unsigned int x = 0; x < bottomLeftRadiusWithPadding; x++)
        {
            uint32_t alpha = alphaMemory[((contextHeight - bottomLeftRadiusWithPadding) + (y - bottomLeftRadiusOriginY)) * contextStride + x * 4 + 3];
            uint32_t pixel = *((uint32_t *)(&memory[y * stride + x * 4]));
            
            pixel = (alpha << 24) | (((((pixel >> 16) & 0xff) * alpha) >> 8) << 16) | (((((pixel >> 8) & 0xff) * alpha) >> 8) << 8) | (((((pixel >> 0) & 0xff) * alpha) >> 8) << 0);
            pixel = alphaComposePremultipliedPixels(*((uint32_t *)&contextMemory[((contextHeight - bottomLeftRadiusWithPadding) + (y - bottomLeftRadiusOriginY)) * contextStride + x * 4]), pixel);
            *((uint32_t *)(&memory[y * stride + x * 4])) = pixel;
        }
    }
    
    const unsigned int bottomRightRadiusWithPadding = bottomRightRadius;
    const unsigned int bottomRightRadiusOriginX = width - bottomRightRadiusWithPadding;
    const unsigned int bottomRightRadiusOriginY = height - bottomRightRadiusWithPadding;
    for (unsigned int y = bottomRightRadiusOriginY; y < height; y++)
    {
        for (unsigned int x = bottomRightRadiusOriginX; x < width; x++)
        {
            uint32_t alpha = alphaMemory[((contextHeight - bottomRightRadiusWithPadding) + (y - bottomRightRadiusOriginY)) * contextStride + ((contextWidth - bottomRightRadiusWithPadding) + (x - bottomRightRadiusOriginX)) * 4 + 3];
            uint32_t pixel = *((uint32_t *)(&memory[y * stride + x * 4]));
            
            pixel = (alpha << 24) | (((((pixel >> 16) & 0xff) * alpha) >> 8) << 16) | (((((pixel >> 8) & 0xff) * alpha) >> 8) << 8) | (((((pixel >> 0) & 0xff) * alpha) >> 8) << 0);
            pixel = alphaComposePremultipliedPixels(*((uint32_t *)&contextMemory[((contextHeight - bottomRightRadiusWithPadding) + (y - bottomRightRadiusOriginY)) * contextStride + ((contextWidth - bottomRightRadiusWithPadding) + (x - bottomRightRadiusOriginX)) * 4]), pixel);
            *((uint32_t *)(&memory[y * stride + x * 4])) = pixel;
        }
    }
    
    free(alphaMemory);
    free(contextMemory);
}

static int16_t *brightenMatrix(int32_t *outDivisor)
{
    static int16_t saturationMatrix[16];
    static const int32_t divisor = 256;
    
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^
    {
        CGFloat s = 2.6f;
        CGFloat offset = 0.02f;
        CGFloat factor = 1.3f;
        CGFloat satMatrix[] = {
            0.0722f + 0.9278f * s,  0.0722f - 0.0722f * s,  0.0722f - 0.0722f * s,  0,
            0.7152f - 0.7152f * s,  0.7152f + 0.2848f * s,  0.7152f - 0.7152f * s,  0,
            0.2126f - 0.2126f * s,  0.2126f - 0.2126f * s,  0.2126f + 0.7873f * s,  0,
            0.0f,                    0.0f,                    0.0f,  1,
        };
        CGFloat contrastMatrix[] = {
            factor, 0.0f, 0.0f, 0.0f,
            0.0f, factor, 0.0f, 0.0f,
            0.0f, 0.0f, factor, 0.0f,
            offset, offset, offset, 1.0f
        };
        CGFloat colorMatrix[16];
        matrixMul(satMatrix, contrastMatrix, colorMatrix);
        
        NSUInteger matrixSize = sizeof(colorMatrix) / sizeof(colorMatrix[0]);
        for (NSUInteger i = 0; i < matrixSize; ++i) {
            saturationMatrix[i] = (int16_t)CGRound(colorMatrix[i] * divisor);
        }
    });
    
    if (outDivisor != NULL)
        *outDivisor = divisor;
    
    return saturationMatrix;
}

static int16_t *lightBrightenMatrix(int32_t *outDivisor)
{
    static int16_t saturationMatrix[16];
    static const int32_t divisor = 256;
    
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^
    {
        CGFloat s = 1.8f;
        CGFloat offset = 0.02f;
        CGFloat factor = 1.1f;
        CGFloat satMatrix[] = {
            0.0722f + 0.9278f * s,  0.0722f - 0.0722f * s,  0.0722f - 0.0722f * s,  0,
            0.7152f - 0.7152f * s,  0.7152f + 0.2848f * s,  0.7152f - 0.7152f * s,  0,
            0.2126f - 0.2126f * s,  0.2126f - 0.2126f * s,  0.2126f + 0.7873f * s,  0,
            0.0f,                    0.0f,                    0.0f,  1,
        };
        CGFloat contrastMatrix[] = {
            factor, 0.0f, 0.0f, 0.0f,
            0.0f, factor, 0.0f, 0.0f,
            0.0f, 0.0f, factor, 0.0f,
            offset, offset, offset, 1.0f
        };
        CGFloat colorMatrix[16];
        matrixMul(satMatrix, contrastMatrix, colorMatrix);
        
        NSUInteger matrixSize = sizeof(colorMatrix) / sizeof(colorMatrix[0]);
        for (NSUInteger i = 0; i < matrixSize; ++i) {
            saturationMatrix[i] = (int16_t)CGRound(colorMatrix[i] * divisor);
        }
    });
    
    if (outDivisor != NULL)
        *outDivisor = divisor;
    
    return saturationMatrix;
}

static int16_t *secretMatrix(int32_t *outDivisor)
{
    static int16_t saturationMatrix[16];
    static const int32_t divisor = 256;
    
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^
    {
        CGFloat s = 1.6f;
        CGFloat offset = 0.0f;
        CGFloat factor = 1.3f;
        CGFloat satMatrix[] = {
            0.0722f + 0.9278f * s,  0.0722f - 0.0722f * s,  0.0722f - 0.0722f * s,  0,
            0.7152f - 0.7152f * s,  0.7152f + 0.2848f * s,  0.7152f - 0.7152f * s,  0,
            0.2126f - 0.2126f * s,  0.2126f - 0.2126f * s,  0.2126f + 0.7873f * s,  0,
            0.0f,                    0.0f,                    0.0f,  1,
        };
        CGFloat contrastMatrix[] = {
            factor, 0.0f, 0.0f, 0.0f,
            0.0f, factor, 0.0f, 0.0f,
            0.0f, 0.0f, factor, 0.0f,
            offset, offset, offset, 1.0f
        };
        CGFloat colorMatrix[16];
        matrixMul(satMatrix, contrastMatrix, colorMatrix);
        
        NSUInteger matrixSize = sizeof(colorMatrix) / sizeof(colorMatrix[0]);
        for (NSUInteger i = 0; i < matrixSize; ++i) {
            saturationMatrix[i] = (int16_t)CGRound(colorMatrix[i] * divisor);
        }
    });
    
    if (outDivisor != NULL)
        *outDivisor = divisor;
    
    return saturationMatrix;
}

UIImage *TGAverageColorImage(UIColor *color)
{
    UIGraphicsBeginImageContextWithOptions(CGSizeMake(1.0f, 1.0f), true, 1.0f);
    CGContextRef context = UIGraphicsGetCurrentContext();
    CGContextSetFillColorWithColor(context, [color CGColor]);
    CGContextFillRect(context, CGRectMake(0.0f, 0.0f, 1.0f, 1.0f));
    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    
    return image;
}

UIImage *TGAverageColorRoundImage(UIColor *color, CGSize size)
{
    UIGraphicsBeginImageContextWithOptions(CGSizeMake(size.width, size.height), false, 0.0f);
    CGContextRef context = UIGraphicsGetCurrentContext();
    CGContextSetFillColorWithColor(context, [color CGColor]);
    CGContextFillRect(context, CGRectMake(0.0f, 0.0f, 1.0f, 1.0f));
    CGContextFillEllipseInRect(context, CGRectMake(0.0f, 0.0f, size.width, size.height));
    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    
    return image;
}

UIImage *TGAverageColorAttachmentImage(UIColor *color, bool attachmentBorder, int position)
{
    return TGAverageColorAttachmentWithCornerRadiusImage(color, attachmentBorder, 13, position);
}

UIImage *TGAverageColorAttachmentWithCornerRadiusImage(UIColor *color, bool attachmentBorder, int cornerRadius, int position)
{
    CGFloat scale = TGScreenScaling();
    
    CGSize size = CGSizeMake(36.0f, 36.0f);
    if (cornerRadius > size.width / 2)
        size = CGSizeMake(cornerRadius * 2, cornerRadius * 2);
    
    const struct { int width, height; } targetContextSize = { (int)(size.width * scale), (int)(size.height * scale)};
    
    size_t targetBytesPerRow = ((4 * (int)targetContextSize.width) + 15) & (~15);
    
    void *targetMemory = malloc((int)(targetBytesPerRow * targetContextSize.height));
    
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    CGBitmapInfo bitmapInfo = kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host;
    
    CGContextRef targetContext = CGBitmapContextCreate(targetMemory, (int)targetContextSize.width, (int)targetContextSize.height, 8, targetBytesPerRow, colorSpace, bitmapInfo);
    
    UIGraphicsPushContext(targetContext);
    CGContextTranslateCTM(targetContext, targetContextSize.width / 2.0f, targetContextSize.height / 2.0f);
    CGContextScaleCTM(targetContext, 1.0f, -1.0f);
    CGContextTranslateCTM(targetContext, -targetContextSize.width / 2.0f, -targetContextSize.height / 2.0f);
    CGContextScaleCTM(targetContext, scale, scale);
    
    CGColorSpaceRelease(colorSpace);
    
    CGContextSetBlendMode(targetContext, kCGBlendModeCopy);
    
    CGContextSetFillColorWithColor(targetContext, [color CGColor]);
    CGContextFillRect(targetContext, CGRectMake(0.0f, 0.0f, targetContextSize.width, targetContextSize.height));
    
    if (attachmentBorder) {
        addAttachmentImageCorners(targetMemory, targetContextSize.width, targetContextSize.height, (unsigned int)targetBytesPerRow, position, 1.0f);
    }
    else if (cornerRadius > 0) {
        TGAddImageCorners(targetMemory, targetContextSize.width, targetContextSize.height, (int)targetBytesPerRow, cornerRadius, position);
    }
    
    UIGraphicsPopContext();
    
    CGImageRef bitmapImage = CGBitmapContextCreateImage(targetContext);
    UIImage *image = [[[UIImage alloc] initWithCGImage:bitmapImage scale:scale orientation:UIImageOrientationUp] stretchableImageWithLeftCapWidth:(int)(targetContextSize.width / scale / 2) topCapHeight:(int)(targetContextSize.height / scale / 2)];
    CGImageRelease(bitmapImage);
    
    CGContextRelease(targetContext);
    free(targetMemory);
    
    /*CGFloat matrix[16];
    int32_t divisor = 256;
    int16_t *integerMatrix = brightenMatrix(&divisor);
    
    for (int i = 0; i < 16; i++)
    {
        matrix[i] = integerMatrix[i] / (CGFloat)divisor;
    }
    
    CGFloat vector[4];
    [color getRed:&vector[3] green:&vector[2] blue:&vector[1] alpha:&vector[0]];
    
    CGFloat resultColor[4];
    matrixVectorMul(matrix, vector, resultColor);
    
    UIImage *genericCircleImage = nil;
    UIGraphicsBeginImageContextWithOptions(CGSizeMake(50.0f, 50.0f), false, scale);
    CGContextRef circleContext = UIGraphicsGetCurrentContext();
    CGContextSetFillColorWithColor(circleContext, [[UIColor alloc] initWithRed:vector[3] green:vector[2] blue:vector[1] alpha:vector[0]].CGColor);
    CGContextFillEllipseInRect(circleContext, CGRectMake(0.0f, 0.0f, 50.0f, 50.0f));
    genericCircleImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    [image setActionCircleImage:genericCircleImage];*/
    
    return image;
}

static void modifyAndBlurImage(void *pixels, unsigned int width, unsigned int height, unsigned int stride, bool strongBlur, int16_t *matrix)
{
    unsigned int tempWidth = width / 6;
    unsigned int tempHeight = height / 6;
    unsigned int tempStride = ((4 * tempWidth + 15) & (~15));
    void *tempPixels = malloc(tempStride * tempHeight);
    
    vImage_Buffer srcBuffer;
    srcBuffer.width = width;
    srcBuffer.height = height;
    srcBuffer.rowBytes = stride;
    srcBuffer.data = pixels;
    
    vImage_Buffer dstBuffer;
    dstBuffer.width = tempWidth;
    dstBuffer.height = tempHeight;
    dstBuffer.rowBytes = tempStride;
    dstBuffer.data = tempPixels;
    
    vImageScale_ARGB8888(&srcBuffer, &dstBuffer, NULL, kvImageDoNotTile);
    
    fastBlurMore(tempWidth, tempHeight, tempStride, tempPixels);
    if (strongBlur)
        fastBlur(tempWidth, tempHeight, tempStride, tempPixels);
    
    int32_t divisor = 256;
    vImageMatrixMultiply_ARGB8888(&dstBuffer, &dstBuffer, matrix, divisor, NULL, NULL, kvImageDoNotTile);
    vImageScale_ARGB8888(&dstBuffer, &srcBuffer, NULL, kvImageDoNotTile);
    
    free(tempPixels);
}

static void modifyImage(void *pixels, unsigned int width, unsigned int height, unsigned int stride, int16_t *matrix)
{
    vImage_Buffer dstBuffer;
    dstBuffer.width = width;
    dstBuffer.height = height;
    dstBuffer.rowBytes = stride;
    dstBuffer.data = pixels;
    
    int32_t divisor = 256;
    vImageMatrixMultiply_ARGB8888(&dstBuffer, &dstBuffer, matrix, divisor, NULL, NULL, kvImageDoNotTile);
}

static void brightenAndBlurImage(void *pixels, unsigned int width, unsigned int height, unsigned int stride, bool strongBlur)
{
    modifyAndBlurImage(pixels, width, height, stride, strongBlur, brightenMatrix(NULL));
}

static void brightenImage(void *pixels, unsigned int width, unsigned int height, unsigned int stride)
{
    modifyImage(pixels, width, height, stride, lightBrightenMatrix(NULL));
}

TGStaticBackdropAreaData *createImageBackdropArea(uint8_t *sourceImageMemory, int sourceImageWidth, int sourceImageHeight, int sourceImageStride, CGSize originalSize, CGRect sourceImageRect)
{
    CGFloat scale = TGIsRetina() ? 2.0f : 1.0f;
    
    const struct { int width, height; } contextSize = { (int)(sourceImageRect.size.width / 2), (int)(sourceImageRect.size.height / 2) };
    size_t bytesPerRow = ((4 * (int)contextSize.width) + 15) & (~15);
    
    CGFloat scalingFactor = contextSize.width / sourceImageRect.size.width;
    
    void *memory = malloc((int)(bytesPerRow * contextSize.height));
    memset(memory, 0x00, (int)(bytesPerRow * contextSize.height));
    
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    CGBitmapInfo bitmapInfo = kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host;
    CGContextRef context = CGBitmapContextCreate(memory, (int)contextSize.width, (int)contextSize.height, 8, bytesPerRow, colorSpace, bitmapInfo);
    CGColorSpaceRelease(colorSpace);
    UIGraphicsPushContext(context);
    
    CGContextTranslateCTM(context, contextSize.width / 2.0f, contextSize.height / 2.0f);
    CGContextScaleCTM(context, 1.0f, -1.0f);
    CGContextTranslateCTM(context, -contextSize.width / 2.0f, -contextSize.height / 2.0f);
    
    CGRect imageRect = CGRectMake(-sourceImageRect.origin.x * scalingFactor, -sourceImageRect.origin.y * scalingFactor, originalSize.width * scalingFactor, originalSize.height * scalingFactor);
    
    float luminance = 0.0f;
    float realLuminance = 0.0f;
    float variance = 0.0f;
    
    if (sourceImageMemory != NULL)
    {
        fastScaleImage(sourceImageMemory, sourceImageWidth, sourceImageHeight, sourceImageStride, memory, contextSize.width, contextSize.height, (int)bytesPerRow, imageRect);
    }
    
    /*if (luminance > 0.8f)
        modifyAndBlurImage(memory, contextSize.width, contextSize.height, bytesPerRow, false, brightenTimestampMatrix(NULL));
    else
        modifyAndBlurImage(memory, contextSize.width, contextSize.height, bytesPerRow, false, darkenTimestampMatrix(NULL));*/
    
    fastBlur(contextSize.width, contextSize.height, (int)bytesPerRow, memory);
    fastBlur(contextSize.width, contextSize.height, (int)bytesPerRow, memory);
    computeImageVariance(memory, contextSize.width, contextSize.height, (int)bytesPerRow, &variance, &luminance, &realLuminance);
    
    if ((variance >= 0.009f && realLuminance > 0.7f) || variance >= 0.05f)
    {
        uint32_t color = TGImageAverageColor(memory, contextSize.width, contextSize.height, (int)bytesPerRow);
        //color = 0xff00ffff;
        CGContextSetFillColorWithColor(context, UIColorRGBA(color, 0.7f).CGColor);
        CGContextFillRect(context, CGRectMake(0, 0, contextSize.width, contextSize.height));
        
        uint32_t r = (color >> 16) & 0xff;
        uint32_t g = (color >> 8) & 0xff;
        uint32_t b = color & 0xff;
        
        uint32_t pixelLuminance = (uint8_t)((r * 299 + g * 587 + b * 114) / 1000);
        luminance = pixelLuminance / 255.0f;
        if (luminance < 0.85f)
            luminance = 0.0f;
    }
    
    CGImageRef bitmapImage = CGBitmapContextCreateImage(context);
    UIImage *image = [[UIImage alloc] initWithCGImage:bitmapImage scale:scale orientation:UIImageOrientationUp];
    CGImageRelease(bitmapImage);
    
    UIGraphicsPopContext();
    CFRelease(context);
    free(memory);
    
    TGStaticBackdropAreaData *backdropArea = [[TGStaticBackdropAreaData alloc] initWithBackground:image mappedRect:CGRectMake(sourceImageRect.origin.x / originalSize.width, sourceImageRect.origin.y / originalSize.height, sourceImageRect.size.width / originalSize.width, sourceImageRect.size.height / originalSize.height)];
    backdropArea.luminance = luminance;
    
    return backdropArea;
}

TGStaticBackdropAreaData *createTimestampBackdropArea(uint8_t *sourceImageMemory, int sourceImageWidth, int sourceImageHeight, int sourceImageStride, CGSize originalSize)
{
    const int extraRadius = 0.0f;
    const struct { int width, height; } unscaledSize = { 84 + extraRadius * 2, 18 };
    const struct { int right, bottom; } padding = { 6 - extraRadius, 6 };

    return createImageBackdropArea(sourceImageMemory, sourceImageWidth, sourceImageHeight, sourceImageStride, originalSize, CGRectMake(originalSize.width - padding.right - unscaledSize.width, originalSize.height - padding.bottom - unscaledSize.height, unscaledSize.width, unscaledSize.height));
}

TGStaticBackdropAreaData *createAdditionalDataBackdropArea(uint8_t *sourceImageMemory, int sourceImageWidth, int sourceImageHeight, int sourceImageStride, CGSize originalSize)
{
    const int extraRadius = 0.0f;
    const struct { int width, height; } unscaledSize = { 160 + extraRadius * 2, 18 };
    const struct { int left, top; } padding = { 6 - extraRadius, 6 };
    
    return createImageBackdropArea(sourceImageMemory, sourceImageWidth, sourceImageHeight, sourceImageStride, originalSize, CGRectMake(padding.left, padding.top, unscaledSize.width, unscaledSize.height));
}

UIImage *TGBlurredAttachmentImage(UIImage *source, CGSize size, uint32_t *averageColor, bool attachmentBorder, int position)
{
    return TGBlurredAttachmentWithCornerRadiusImage(source, size, averageColor, attachmentBorder, attachmentBorder ? 14 : 15, position);
}

UIImage *TGBlurredAttachmentWithCornerRadiusImage(UIImage *source, CGSize size, uint32_t *averageColor, bool attachmentBorder, int cornerRadius, int position)
{
    CGFloat scale = TGScreenScaling(); // //TGIsRetina() ? 2.0f : 1.0f;
    
    CGSize fittedSize = fitSize(size, CGSizeMake(90, 90));
    
    CGFloat actionCircleDiameter = 50.0f;
    
    const struct { int width, height; } blurredContextSize = { (int)fittedSize.width, (int)fittedSize.height };
    const struct { int width, height; } targetContextSize = { (int)(size.width * scale), (int)(size.height * scale)};
    const struct { int width, height; } actionCircleContextSize = { (int)(actionCircleDiameter * scale), (int)(actionCircleDiameter * scale) };
    
    size_t blurredBytesPerRow = ((4 * (int)blurredContextSize.width) + 15) & (~15);
    size_t targetBytesPerRow = ((4 * (int)targetContextSize.width) + 15) & (~15);
    size_t actionCircleBytesPerRow = ((4 * (int)actionCircleContextSize.width) + 15) & (~15);
    
    void *blurredMemory = malloc((int)(blurredBytesPerRow * blurredContextSize.height));
    void *targetMemory = malloc((int)(targetBytesPerRow * targetContextSize.height));
    void *actionCircleMemory = malloc(((int)(actionCircleBytesPerRow * actionCircleContextSize.height)));
    
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    CGBitmapInfo bitmapInfo = kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host;
    
    CGContextRef blurredContext = CGBitmapContextCreate(blurredMemory, (int)blurredContextSize.width, (int)blurredContextSize.height, 8, blurredBytesPerRow, colorSpace, bitmapInfo);
    CGContextRef targetContext = CGBitmapContextCreate(targetMemory, (int)targetContextSize.width, (int)targetContextSize.height, 8, targetBytesPerRow, colorSpace, bitmapInfo);
    CGContextRef actionCircleContext = CGBitmapContextCreate(actionCircleMemory, (int)actionCircleContextSize.width, (int)actionCircleContextSize.height, 8, actionCircleBytesPerRow, colorSpace, bitmapInfo);
    
    CGColorSpaceRelease(colorSpace);
    
    UIGraphicsPushContext(blurredContext);
    CGContextTranslateCTM(blurredContext, blurredContextSize.width / 2.0f, blurredContextSize.height / 2.0f);
    CGContextScaleCTM(blurredContext, 1.0f, -1.0f);
    CGContextTranslateCTM(blurredContext, -blurredContextSize.width / 2.0f, -blurredContextSize.height / 2.0f);
    CGContextSetInterpolationQuality(blurredContext, kCGInterpolationLow);
    [source drawInRect:CGRectMake(0, 0, blurredContextSize.width, blurredContextSize.height) blendMode:kCGBlendModeCopy alpha:1.0f];
    UIGraphicsPopContext();
    
    fastBlur((int)blurredContextSize.width, (int)blurredContextSize.height, (int)blurredBytesPerRow, blurredMemory);
    
    if (averageColor != NULL)
    {
        *averageColor = TGImageAverageColor(blurredMemory, blurredContextSize.width, blurredContextSize.height, (int)blurredBytesPerRow);
    }
    
    vImage_Buffer srcBuffer;
    srcBuffer.width = blurredContextSize.width;
    srcBuffer.height = blurredContextSize.height;
    srcBuffer.rowBytes = blurredBytesPerRow;
    srcBuffer.data = blurredMemory;
    
    vImage_Buffer dstBuffer;
    dstBuffer.width = targetContextSize.width;
    dstBuffer.height = targetContextSize.height;
    dstBuffer.rowBytes = targetBytesPerRow;
    dstBuffer.data = targetMemory;
    
    vImageScale_ARGB8888(&srcBuffer, &dstBuffer, NULL, kvImageDoNotTile);
    
    CGContextRelease(blurredContext);
    free(blurredMemory);
    
    UIGraphicsPushContext(actionCircleContext);
    CGContextTranslateCTM(actionCircleContext, actionCircleContextSize.width / 2.0f, actionCircleContextSize.height / 2.0f);
    CGContextScaleCTM(actionCircleContext, 1.0f, -1.0f);
    CGContextTranslateCTM(actionCircleContext, -actionCircleContextSize.width / 2.0f, -actionCircleContextSize.height / 2.0f);
    
    CGContextSetInterpolationQuality(actionCircleContext, kCGInterpolationLow);
    CGContextSetBlendMode(actionCircleContext, kCGBlendModeCopy);
    
    [source drawInRect:CGRectMake((actionCircleContextSize.width - targetContextSize.width) / 2.0f, (actionCircleContextSize.height - targetContextSize.height) / 2.0f, targetContextSize.width, targetContextSize.height) blendMode:kCGBlendModeCopy alpha:1.0f];
    UIGraphicsPopContext();
    
    brightenAndBlurImage(actionCircleMemory, actionCircleContextSize.width, actionCircleContextSize.height, (int)actionCircleBytesPerRow, scale < 2);
    
    CGContextBeginPath(actionCircleContext);
    CGContextAddRect(actionCircleContext, CGRectMake(0.0f, 0.0f, actionCircleContextSize.width, actionCircleContextSize.height));
    CGContextAddEllipseInRect(actionCircleContext, CGRectMake(0.0f, 0.0f, actionCircleContextSize.width, actionCircleContextSize.height));
    CGContextClosePath(actionCircleContext);
    
    CGContextSetFillColorWithColor(actionCircleContext, [UIColor clearColor].CGColor);
    CGContextEOFillPath(actionCircleContext);
    
    CGImageRef actionCircleBitmapImage = CGBitmapContextCreateImage(actionCircleContext);
    UIImage *actionCircleImage = [[UIImage alloc] initWithCGImage:actionCircleBitmapImage scale:scale orientation:UIImageOrientationUp];
    CGImageRelease(actionCircleBitmapImage);
    
    CGContextRelease(actionCircleContext);
    free(actionCircleMemory);
    
    TGStaticBackdropAreaData *timestampBackdropArea = createTimestampBackdropArea(targetMemory, targetContextSize.width, targetContextSize.height, (int)targetBytesPerRow, CGSizeMake(size.width, size.height));
    TGStaticBackdropAreaData *additionalDataBackdropArea = createAdditionalDataBackdropArea(targetMemory, targetContextSize.width, targetContextSize.height, (int)targetBytesPerRow, CGSizeMake(size.width, size.height));
    
    if (attachmentBorder)
    {
        addAttachmentImageCorners(targetMemory, targetContextSize.width, targetContextSize.height, (int)targetBytesPerRow, position, 1.0f);
    }
    else
    {
        TGAddImageCorners(targetMemory, targetContextSize.width, targetContextSize.height, (int)targetBytesPerRow, cornerRadius, position);
    }
    
    CGImageRef bitmapImage = CGBitmapContextCreateImage(targetContext);
    UIImage *image = [[UIImage alloc] initWithCGImage:bitmapImage];
    CGImageRelease(bitmapImage);
    
    CGContextRelease(targetContext);
    free(targetMemory);
    
    TGStaticBackdropImageData *backdropData = [[TGStaticBackdropImageData alloc] init];
    [backdropData setBackdropArea:[[TGStaticBackdropAreaData alloc] initWithBackground:actionCircleImage] forKey:TGStaticBackdropMessageActionCircle];
    
    [backdropData setBackdropArea:timestampBackdropArea forKey:TGStaticBackdropMessageTimestamp];
    [backdropData setBackdropArea:additionalDataBackdropArea forKey:TGStaticBackdropMessageAdditionalData];
    
    [image setStaticBackdropImageData:backdropData];
    
    return image;
}

UIImage *TGSecretBlurredAttachmentImage(UIImage *source, CGSize size, uint32_t *averageColor, bool attachmentBorder, int position)
{
    return TGSecretBlurredAttachmentWithCornerRadiusImage(source, size, averageColor, attachmentBorder, 13, position);
}

#if DEBUG
@interface DebugTGSecretBlurredAttachmentWithCornerRadiusImage : UIImage
@end

@implementation DebugTGSecretBlurredAttachmentWithCornerRadiusImage
@end
#endif

UIImage *TGSecretBlurredAttachmentWithCornerRadiusImage(UIImage *source, CGSize size, uint32_t *averageColor, bool attachmentBorder, CGFloat cornerRadius, int position)
{
    CGFloat scale = TGScreenScaling(); //TGIsRetina() ? 2.0f : 1.0f;
    
    CGSize fittedSize = fitSize(size, CGSizeMake(40, 40));
    
    CGFloat actionCircleDiameter = 50.0f;
    
    const struct { int width, height; } blurredContextSize = { (int)fittedSize.width, (int)fittedSize.height };
    const struct { int width, height; } targetContextSize = { (int)(size.width * scale), (int)(size.height * scale)};
    const struct { int width, height; } actionCircleContextSize = { (int)(actionCircleDiameter * scale), (int)(actionCircleDiameter * scale) };
    
    size_t blurredBytesPerRow = ((4 * (int)blurredContextSize.width) + 15) & (~15);
    size_t targetBytesPerRow = ((4 * (int)targetContextSize.width) + 15) & (~15);
    size_t actionCircleBytesPerRow = ((4 * (int)actionCircleContextSize.width) + 15) & (~15);
    
    void *blurredMemory = malloc((int)(blurredBytesPerRow * blurredContextSize.height));
    void *targetMemory = malloc((int)(targetBytesPerRow * targetContextSize.height));
    void *actionCircleMemory = malloc(((int)(actionCircleBytesPerRow * actionCircleContextSize.height)));
    
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    CGBitmapInfo bitmapInfo = kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host;
    
    CGContextRef blurredContext = CGBitmapContextCreate(blurredMemory, (int)blurredContextSize.width, (int)blurredContextSize.height, 8, blurredBytesPerRow, colorSpace, bitmapInfo);
    CGContextRef targetContext = CGBitmapContextCreate(targetMemory, (int)targetContextSize.width, (int)targetContextSize.height, 8, targetBytesPerRow, colorSpace, bitmapInfo);
    CGContextRef actionCircleContext = CGBitmapContextCreate(actionCircleMemory, (int)actionCircleContextSize.width, (int)actionCircleContextSize.height, 8, actionCircleBytesPerRow, colorSpace, bitmapInfo);
    
    CGColorSpaceRelease(colorSpace);
    
    UIGraphicsPushContext(blurredContext);
    CGContextTranslateCTM(blurredContext, blurredContextSize.width / 2.0f, blurredContextSize.height / 2.0f);
    CGContextScaleCTM(blurredContext, 1.0f, -1.0f);
    CGContextTranslateCTM(blurredContext, -blurredContextSize.width / 2.0f, -blurredContextSize.height / 2.0f);
    CGContextSetInterpolationQuality(blurredContext, kCGInterpolationLow);
    [source drawInRect:CGRectMake(0, 0, blurredContextSize.width, blurredContextSize.height) blendMode:kCGBlendModeCopy alpha:1.0f];
    UIGraphicsPopContext();
    
    fastBlurMore((int)blurredContextSize.width, (int)blurredContextSize.height, (int)blurredBytesPerRow, blurredMemory);
    fastBlurMore((int)blurredContextSize.width, (int)blurredContextSize.height, (int)blurredBytesPerRow, blurredMemory);
    fastBlurMore((int)blurredContextSize.width, (int)blurredContextSize.height, (int)blurredBytesPerRow, blurredMemory);
    
    int32_t divisor = 256;
    vImage_Buffer dstBuffer1;
    dstBuffer1.width = (int)blurredContextSize.width;
    dstBuffer1.height = (int)blurredContextSize.height;
    dstBuffer1.rowBytes = blurredBytesPerRow;
    dstBuffer1.data = blurredMemory;
    vImageMatrixMultiply_ARGB8888(&dstBuffer1, &dstBuffer1, secretMatrix(NULL), divisor, NULL, NULL, kvImageDoNotTile);
    
    if (averageColor != NULL)
    {
        *averageColor = TGImageAverageColor(blurredMemory, blurredContextSize.width, blurredContextSize.height, (int)blurredBytesPerRow);
    }
    
    vImage_Buffer srcBuffer;
    srcBuffer.width = blurredContextSize.width;
    srcBuffer.height = blurredContextSize.height;
    srcBuffer.rowBytes = blurredBytesPerRow;
    srcBuffer.data = blurredMemory;
    
    vImage_Buffer dstBuffer;
    dstBuffer.width = targetContextSize.width;
    dstBuffer.height = targetContextSize.height;
    dstBuffer.rowBytes = targetBytesPerRow;
    dstBuffer.data = targetMemory;
    
    vImageScale_ARGB8888(&srcBuffer, &dstBuffer, NULL, kvImageDoNotTile);
    
    CGContextRelease(blurredContext);
    free(blurredMemory);
    
    UIGraphicsPushContext(actionCircleContext);
    CGContextTranslateCTM(actionCircleContext, actionCircleContextSize.width / 2.0f, actionCircleContextSize.height / 2.0f);
    CGContextScaleCTM(actionCircleContext, 1.0f, -1.0f);
    CGContextTranslateCTM(actionCircleContext, -actionCircleContextSize.width / 2.0f, -actionCircleContextSize.height / 2.0f);
    
    CGContextSetInterpolationQuality(actionCircleContext, kCGInterpolationLow);
    CGContextSetBlendMode(actionCircleContext, kCGBlendModeCopy);
    
    [source drawInRect:CGRectMake((actionCircleContextSize.width - targetContextSize.width) / 2.0f, (actionCircleContextSize.height - targetContextSize.height) / 2.0f, targetContextSize.width, targetContextSize.height) blendMode:kCGBlendModeCopy alpha:1.0f];
    UIGraphicsPopContext();
    
    brightenAndBlurImage(actionCircleMemory, actionCircleContextSize.width, actionCircleContextSize.height, (int)actionCircleBytesPerRow, scale < 2);
    
    CGContextBeginPath(actionCircleContext);
    CGContextAddRect(actionCircleContext, CGRectMake(0.0f, 0.0f, actionCircleContextSize.width, actionCircleContextSize.height));
    CGContextAddEllipseInRect(actionCircleContext, CGRectMake(0.0f, 0.0f, actionCircleContextSize.width, actionCircleContextSize.height));
    CGContextClosePath(actionCircleContext);
    
    CGContextSetFillColorWithColor(actionCircleContext, [UIColor clearColor].CGColor);
    CGContextEOFillPath(actionCircleContext);
    
    CGImageRef actionCircleBitmapImage = CGBitmapContextCreateImage(actionCircleContext);
    UIImage *actionCircleImage = [[UIImage alloc] initWithCGImage:actionCircleBitmapImage scale:scale orientation:UIImageOrientationUp];
    CGImageRelease(actionCircleBitmapImage);
    
    CGContextRelease(actionCircleContext);
    free(actionCircleMemory);
    
    TGStaticBackdropAreaData *timestampBackdropArea = createTimestampBackdropArea(targetMemory, targetContextSize.width, targetContextSize.height, (int)targetBytesPerRow, CGSizeMake(size.width, size.height));
    TGStaticBackdropAreaData *additionalDataBackdropArea = createAdditionalDataBackdropArea(targetMemory, targetContextSize.width, targetContextSize.height, (int)targetBytesPerRow, CGSizeMake(size.width, size.height));
    
    if (attachmentBorder) {
        addAttachmentImageCorners(targetMemory, targetContextSize.width, targetContextSize.height, (int)targetBytesPerRow, position, 1.0f);
    } else {
        TGAddImageCorners(targetMemory, targetContextSize.width, targetContextSize.height, (int)targetBytesPerRow, cornerRadius, position);
    }
    
    CGImageRef bitmapImage = CGBitmapContextCreateImage(targetContext);
#if DEBUG
    UIImage *image = [[DebugTGSecretBlurredAttachmentWithCornerRadiusImage alloc] initWithCGImage:bitmapImage];
#else
    UIImage *image = [[UIImage alloc] initWithCGImage:bitmapImage];
#endif
    CGImageRelease(bitmapImage);
    
    CGContextRelease(targetContext);
    free(targetMemory);
    
    TGStaticBackdropImageData *backdropData = [[TGStaticBackdropImageData alloc] init];
    [backdropData setBackdropArea:[[TGStaticBackdropAreaData alloc] initWithBackground:actionCircleImage] forKey:TGStaticBackdropMessageActionCircle];
    
    [backdropData setBackdropArea:timestampBackdropArea forKey:TGStaticBackdropMessageTimestamp];
    [backdropData setBackdropArea:additionalDataBackdropArea forKey:TGStaticBackdropMessageAdditionalData];
    
    [image setStaticBackdropImageData:backdropData];
    
    return image;
}

UIImage *TGBlurredFileImage(UIImage *source, CGSize size, uint32_t *averageColor, int borderRadius)
{
    CGFloat scale = TGScreenScaling(); //TGIsRetina() ? 2.0f : 1.0f;
    
    CGSize fittedSize = fitSize(size, CGSizeMake(90, 90));
    
    CGFloat actionCircleDiameter = 50.0f;
    
    const struct { int width, height; } blurredContextSize = { (int)fittedSize.width, (int)fittedSize.height };
    const struct { int width, height; } targetContextSize = { (int)(size.width * scale), (int)(size.height * scale)};
    const struct { int width, height; } actionCircleContextSize = { (int)(actionCircleDiameter * scale), (int)(actionCircleDiameter * scale) };
    
    size_t blurredBytesPerRow = ((4 * (int)blurredContextSize.width) + 15) & (~15);
    size_t targetBytesPerRow = ((4 * (int)targetContextSize.width) + 15) & (~15);
    size_t actionCircleBytesPerRow = ((4 * (int)actionCircleContextSize.width) + 15) & (~15);
    
    void *blurredMemory = malloc((int)(blurredBytesPerRow * blurredContextSize.height));
    void *targetMemory = malloc((int)(targetBytesPerRow * targetContextSize.height));
    void *actionCircleMemory = malloc(((int)(actionCircleBytesPerRow * actionCircleContextSize.height)));
    
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    CGBitmapInfo bitmapInfo = kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host;
    
    CGContextRef blurredContext = CGBitmapContextCreate(blurredMemory, (int)blurredContextSize.width, (int)blurredContextSize.height, 8, blurredBytesPerRow, colorSpace, bitmapInfo);
    CGContextRef targetContext = CGBitmapContextCreate(targetMemory, (int)targetContextSize.width, (int)targetContextSize.height, 8, targetBytesPerRow, colorSpace, bitmapInfo);
    CGContextRef actionCircleContext = CGBitmapContextCreate(actionCircleMemory, (int)actionCircleContextSize.width, (int)actionCircleContextSize.height, 8, actionCircleBytesPerRow, colorSpace, bitmapInfo);
    
    CGColorSpaceRelease(colorSpace);
    
    UIGraphicsPushContext(blurredContext);
    CGContextTranslateCTM(blurredContext, blurredContextSize.width / 2.0f, blurredContextSize.height / 2.0f);
    CGContextScaleCTM(blurredContext, 1.0f, -1.0f);
    CGContextTranslateCTM(blurredContext, -blurredContextSize.width / 2.0f, -blurredContextSize.height / 2.0f);
    CGContextSetInterpolationQuality(blurredContext, kCGInterpolationLow);
    [source drawInRect:CGRectMake(0, 0, blurredContextSize.width, blurredContextSize.height) blendMode:kCGBlendModeCopy alpha:1.0f];
    UIGraphicsPopContext();
    
    fastBlur((int)blurredContextSize.width, (int)blurredContextSize.height, (int)blurredBytesPerRow, blurredMemory);
    
    if (averageColor != NULL)
    {
        *averageColor = TGImageAverageColor(blurredMemory, blurredContextSize.width, blurredContextSize.height, (int)blurredBytesPerRow);
    }
    
    vImage_Buffer srcBuffer;
    srcBuffer.width = blurredContextSize.width;
    srcBuffer.height = blurredContextSize.height;
    srcBuffer.rowBytes = blurredBytesPerRow;
    srcBuffer.data = blurredMemory;
    
    vImage_Buffer dstBuffer;
    dstBuffer.width = targetContextSize.width;
    dstBuffer.height = targetContextSize.height;
    dstBuffer.rowBytes = targetBytesPerRow;
    dstBuffer.data = targetMemory;
    
    vImageScale_ARGB8888(&srcBuffer, &dstBuffer, NULL, kvImageDoNotTile);
    
    CGContextRelease(blurredContext);
    free(blurredMemory);
    
    UIGraphicsPushContext(actionCircleContext);
    CGContextTranslateCTM(actionCircleContext, actionCircleContextSize.width / 2.0f, actionCircleContextSize.height / 2.0f);
    CGContextScaleCTM(actionCircleContext, 1.0f, -1.0f);
    CGContextTranslateCTM(actionCircleContext, -actionCircleContextSize.width / 2.0f, -actionCircleContextSize.height / 2.0f);
    
    CGContextSetInterpolationQuality(actionCircleContext, kCGInterpolationLow);
    CGContextSetBlendMode(actionCircleContext, kCGBlendModeCopy);
    
    [source drawInRect:CGRectMake((actionCircleContextSize.width - targetContextSize.width) / 2.0f, (actionCircleContextSize.height - targetContextSize.height) / 2.0f, targetContextSize.width, targetContextSize.height) blendMode:kCGBlendModeCopy alpha:1.0f];
    UIGraphicsPopContext();
    
    brightenAndBlurImage(actionCircleMemory, actionCircleContextSize.width, actionCircleContextSize.height, (int)actionCircleBytesPerRow, scale < 2);
    
    CGContextBeginPath(actionCircleContext);
    CGContextAddRect(actionCircleContext, CGRectMake(0.0f, 0.0f, actionCircleContextSize.width, actionCircleContextSize.height));
    CGContextAddEllipseInRect(actionCircleContext, CGRectMake(0.0f, 0.0f, actionCircleContextSize.width, actionCircleContextSize.height));
    CGContextClosePath(actionCircleContext);
    
    CGContextSetFillColorWithColor(actionCircleContext, [UIColor clearColor].CGColor);
    CGContextEOFillPath(actionCircleContext);
    
    CGImageRef actionCircleBitmapImage = CGBitmapContextCreateImage(actionCircleContext);
    UIImage *actionCircleImage = [[UIImage alloc] initWithCGImage:actionCircleBitmapImage scale:scale orientation:UIImageOrientationUp];
    CGImageRelease(actionCircleBitmapImage);
    
    CGContextRelease(actionCircleContext);
    free(actionCircleMemory);
    
    TGStaticBackdropAreaData *timestampBackdropArea = createTimestampBackdropArea(targetMemory, targetContextSize.width, targetContextSize.height, (int)targetBytesPerRow, CGSizeMake(size.width, size.height));
    TGStaticBackdropAreaData *additionalDataBackdropArea = createAdditionalDataBackdropArea(targetMemory, targetContextSize.width, targetContextSize.height, (int)targetBytesPerRow, CGSizeMake(size.width, size.height));
    
    if (borderRadius != 0)
    {
        TGAddImageCorners(targetMemory, targetContextSize.width, targetContextSize.height, (int)targetBytesPerRow, (int)(borderRadius), 0);
    }
    
    CGImageRef bitmapImage = CGBitmapContextCreateImage(targetContext);
    UIImage *image = [[UIImage alloc] initWithCGImage:bitmapImage];
    CGImageRelease(bitmapImage);
    
    CGContextRelease(targetContext);
    free(targetMemory);
    
    TGStaticBackdropImageData *backdropData = [[TGStaticBackdropImageData alloc] init];
    [backdropData setBackdropArea:[[TGStaticBackdropAreaData alloc] initWithBackground:actionCircleImage] forKey:TGStaticBackdropMessageActionCircle];
    
    [backdropData setBackdropArea:timestampBackdropArea forKey:TGStaticBackdropMessageTimestamp];
    [backdropData setBackdropArea:additionalDataBackdropArea forKey:TGStaticBackdropMessageAdditionalData];
    
    [image setStaticBackdropImageData:backdropData];
    
    return image;
}

UIImage *TGBlurredAlphaImage(UIImage *source, CGSize size)
{
    CGFloat scale = TGIsRetina() ? 2.0f : 1.0f;
    
    CGSize fittedSize = fitSize(size, CGSizeMake(90, 90));
    
    const struct { int width, height; } blurredContextSize = { (int)fittedSize.width, (int)fittedSize.height };
    const struct { int width, height; } targetContextSize = { (int)(size.width * scale), (int)(size.height * scale)};
    
    size_t blurredBytesPerRow = ((4 * (int)blurredContextSize.width) + 15) & (~15);
    size_t targetBytesPerRow = ((4 * (int)targetContextSize.width) + 15) & (~15);
    
    void *blurredMemory = malloc((int)(blurredBytesPerRow * blurredContextSize.height));
    memset(blurredMemory, 0, blurredBytesPerRow * blurredContextSize.height);
    void *targetMemory = malloc((int)(targetBytesPerRow * targetContextSize.height));
    
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    CGBitmapInfo bitmapInfo = kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host;
    
    CGContextRef blurredContext = CGBitmapContextCreate(blurredMemory, (int)blurredContextSize.width, (int)blurredContextSize.height, 8, blurredBytesPerRow, colorSpace, bitmapInfo);
    CGContextRef targetContext = CGBitmapContextCreate(targetMemory, (int)targetContextSize.width, (int)targetContextSize.height, 8, targetBytesPerRow, colorSpace, bitmapInfo);
    
    CGColorSpaceRelease(colorSpace);
    
    UIGraphicsPushContext(blurredContext);
    CGContextTranslateCTM(blurredContext, blurredContextSize.width / 2.0f, blurredContextSize.height / 2.0f);
    CGContextScaleCTM(blurredContext, 1.0f, -1.0f);
    CGContextTranslateCTM(blurredContext, -blurredContextSize.width / 2.0f, -blurredContextSize.height / 2.0f);
    CGContextSetInterpolationQuality(blurredContext, kCGInterpolationLow);
    [source drawInRect:CGRectMake(6, 6, blurredContextSize.width - 12, blurredContextSize.height - 12) blendMode:kCGBlendModeCopy alpha:1.0f];
    UIGraphicsPopContext();
    
    vImage_Buffer srcBuffer;
    srcBuffer.width = blurredContextSize.width;
    srcBuffer.height = blurredContextSize.height;
    srcBuffer.rowBytes = blurredBytesPerRow;
    srcBuffer.data = blurredMemory;
    
    {
        vImage_Buffer dstBuffer;
        dstBuffer.width = blurredContextSize.width;
        dstBuffer.height = blurredContextSize.height;
        dstBuffer.rowBytes = blurredBytesPerRow;
        dstBuffer.data = targetMemory;
        
        int boxSize = (int)(0.02f * 100);
        boxSize = boxSize - (boxSize % 2) + 1;
        
        vImageBoxConvolve_ARGB8888(&srcBuffer, &dstBuffer, NULL, 0, 0, boxSize, boxSize, NULL, kvImageEdgeExtend);
        vImageBoxConvolve_ARGB8888(&dstBuffer, &srcBuffer, NULL, 0, 0, boxSize, boxSize, NULL, kvImageEdgeExtend);
    }
    
    vImage_Buffer dstBuffer;
    dstBuffer.width = targetContextSize.width;
    dstBuffer.height = targetContextSize.height;
    dstBuffer.rowBytes = targetBytesPerRow;
    dstBuffer.data = targetMemory;
    
    vImageScale_ARGB8888(&srcBuffer, &dstBuffer, NULL, kvImageDoNotTile);
    
    CGContextRelease(blurredContext);
    free(blurredMemory);
    
    CGImageRef bitmapImage = CGBitmapContextCreateImage(targetContext);
    UIImage *image = [[UIImage alloc] initWithCGImage:bitmapImage];
    CGImageRelease(bitmapImage);
    
    CGContextRelease(targetContext);
    free(targetMemory);
    
    [image setExtendedEdgeInsets:UIEdgeInsetsMake(12.0f, 12.0f, 12.0f, 12.0f)];
    
    return image;
}

UIImage *TGBlurredRectangularImage(UIImage *source, bool more, CGSize size, CGSize renderSize, uint32_t *averageColor, void (^pixelProcessingBlock)(void *, int, int, int))
{
    CGSize fittedSize = fitSize(size, CGSizeMake(90, 90));
    if ((int)(fittedSize.width) % 2 != 0) {
        fittedSize.width += 1.0;
    }
    CGSize fittedRenderSize = CGSizeMake(fittedSize.width / size.width * renderSize.width, fittedSize.height / size.height * renderSize.height);
    
    const struct { int width, height; } blurredContextSize = { (int)fittedSize.width, (int)fittedSize.height };
    const struct { int width, height; } targetContextSize = { (int)(size.width), (int)(size.height)};
    
    size_t blurredBytesPerRow = ((4 * (int)blurredContextSize.width) + 15) & (~15);
    size_t targetBytesPerRow = ((4 * (int)targetContextSize.width) + 15) & (~15);
    
    void *blurredMemory = malloc((int)(blurredBytesPerRow * blurredContextSize.height));
    void *targetMemory = malloc((int)(targetBytesPerRow * targetContextSize.height));
    
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    CGBitmapInfo bitmapInfo = kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host;
    
    CGContextRef blurredContext = CGBitmapContextCreate(blurredMemory, (int)blurredContextSize.width, (int)blurredContextSize.height, 8, blurredBytesPerRow, colorSpace, bitmapInfo);
    CGContextRef targetContext = CGBitmapContextCreate(targetMemory, (int)targetContextSize.width, (int)targetContextSize.height, 8, targetBytesPerRow, colorSpace, bitmapInfo);
    
    CGColorSpaceRelease(colorSpace);
    
    UIGraphicsPushContext(blurredContext);
    CGContextTranslateCTM(blurredContext, blurredContextSize.width / 2.0f, blurredContextSize.height / 2.0f);
    CGContextScaleCTM(blurredContext, 1.0f, -1.0f);
    CGContextTranslateCTM(blurredContext, -blurredContextSize.width / 2.0f, -blurredContextSize.height / 2.0f);
    CGContextSetInterpolationQuality(blurredContext, kCGInterpolationLow);
    [source drawInRect:CGRectMake((blurredContextSize.width - fittedRenderSize.width) / 2.0f, (blurredContextSize.height - fittedRenderSize.height) / 2.0f, fittedRenderSize.width, fittedRenderSize.height) blendMode:kCGBlendModeCopy alpha:1.0f];
    UIGraphicsPopContext();
    
    if (more) {
        fastBlurMore((int)blurredContextSize.width, (int)blurredContextSize.height, (int)blurredBytesPerRow, blurredMemory);
        fastBlurMore((int)blurredContextSize.width, (int)blurredContextSize.height, (int)blurredBytesPerRow, blurredMemory);
    } else {
        fastBlur((int)blurredContextSize.width, (int)blurredContextSize.height, (int)blurredBytesPerRow, blurredMemory);
    }
    
    if (averageColor != NULL)
    {
        *averageColor = TGImageAverageColor(blurredMemory, blurredContextSize.width, blurredContextSize.height, (int)blurredBytesPerRow);
    }
    
    vImage_Buffer srcBuffer;
    srcBuffer.width = blurredContextSize.width;
    srcBuffer.height = blurredContextSize.height;
    srcBuffer.rowBytes = blurredBytesPerRow;
    srcBuffer.data = blurredMemory;
    
    vImage_Buffer dstBuffer;
    dstBuffer.width = targetContextSize.width;
    dstBuffer.height = targetContextSize.height;
    dstBuffer.rowBytes = targetBytesPerRow;
    dstBuffer.data = targetMemory;
    
    vImageScale_ARGB8888(&srcBuffer, &dstBuffer, NULL, kvImageDoNotTile);
    
    CGContextRelease(blurredContext);
    free(blurredMemory);
    
    if (pixelProcessingBlock)
    {
        pixelProcessingBlock(targetMemory, targetContextSize.width, targetContextSize.height, (int)targetBytesPerRow);
    }
    
    CGImageRef bitmapImage = CGBitmapContextCreateImage(targetContext);
    UIImage *image = [[UIImage alloc] initWithCGImage:bitmapImage];
    CGImageRelease(bitmapImage);
    
    CGContextRelease(targetContext);
    free(targetMemory);
    
    return image;
}

UIImage *TGLoadedAttachmentImage(UIImage *source, CGSize size, uint32_t *averageColor, bool attachmentBorder, int position)
{
    return TGLoadedAttachmentWithCornerRadiusImage(source, size, averageColor, attachmentBorder, attachmentBorder ? 14 : 15, 0, position);
}

UIImage *TGLoadedAttachmentWithCornerRadiusImage(UIImage *source, CGSize size, uint32_t *averageColor, bool attachmentBorder, int cornerRadius, int inset, int position)
{
    CGFloat scale = TGScreenScaling();
    if (cornerRadius < 0)
        cornerRadius = 0;
    
    CGFloat actionCircleDiameter = 50.0f;
    
    const struct { int width, height; } targetContextSize = { (int)(size.width * scale), (int)(size.height * scale) };
    const struct { int width, height; } actionCircleContextSize = { (int)(actionCircleDiameter * scale), (int)(actionCircleDiameter * scale) };
    
    size_t targetBytesPerRow = ((4 * (int)targetContextSize.width) + 15) & (~15);
    size_t actionCircleBytesPerRow = ((4 * (int)actionCircleContextSize.width) + 15) & (~15);
    
    void *targetMemory = malloc((int)(targetBytesPerRow * targetContextSize.height));
    void *actionCircleMemory = malloc(((int)(actionCircleBytesPerRow * actionCircleContextSize.height)));
    
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    CGBitmapInfo bitmapInfo = kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host;
    
    CGContextRef targetContext = CGBitmapContextCreate(targetMemory, (int)targetContextSize.width, (int)targetContextSize.height, 8, targetBytesPerRow, colorSpace, bitmapInfo);
    CGContextRef actionCircleContext = CGBitmapContextCreate(actionCircleMemory, (int)actionCircleContextSize.width, (int)actionCircleContextSize.height, 8, actionCircleBytesPerRow, colorSpace, bitmapInfo);
    
    CGColorSpaceRelease(colorSpace);
    
    UIGraphicsPushContext(actionCircleContext);
    CGContextTranslateCTM(actionCircleContext, actionCircleContextSize.width / 2.0f, actionCircleContextSize.height / 2.0f);
    CGContextScaleCTM(actionCircleContext, 1.0f, -1.0f);
    CGContextTranslateCTM(actionCircleContext, -actionCircleContextSize.width / 2.0f, -actionCircleContextSize.height / 2.0f);
    CGContextSetInterpolationQuality(actionCircleContext, kCGInterpolationLow);
    CGContextSetBlendMode(actionCircleContext, kCGBlendModeCopy);
    
    [source drawInRect:CGRectMake((actionCircleContextSize.width - targetContextSize.width) / 2.0f, (actionCircleContextSize.height - targetContextSize.height) / 2.0f, targetContextSize.width, targetContextSize.height) blendMode:kCGBlendModeCopy alpha:1.0f];
    brightenAndBlurImage(actionCircleMemory, actionCircleContextSize.width, actionCircleContextSize.height, (int)actionCircleBytesPerRow, true);
    
    CGContextBeginPath(actionCircleContext);
    CGContextAddRect(actionCircleContext, CGRectMake(0.0f, 0.0f, actionCircleContextSize.width, actionCircleContextSize.height));
    CGContextAddEllipseInRect(actionCircleContext, CGRectMake(0.0f, 0.0f, actionCircleContextSize.width, actionCircleContextSize.height));
    CGContextClosePath(actionCircleContext);
    
    CGContextSetFillColorWithColor(actionCircleContext, [UIColor clearColor].CGColor);
    CGContextEOFillPath(actionCircleContext);
    
    UIGraphicsPopContext();
    
    CGImageRef actionCircleBitmapImage = CGBitmapContextCreateImage(actionCircleContext);
    UIImage *actionCircleImage = [[UIImage alloc] initWithCGImage:actionCircleBitmapImage scale:scale orientation:UIImageOrientationUp];
    CGImageRelease(actionCircleBitmapImage);
    
    CGContextRelease(actionCircleContext);
    free(actionCircleMemory);
    
    UIGraphicsPushContext(targetContext);
    CGContextTranslateCTM(targetContext, targetContextSize.width / 2.0f, targetContextSize.height / 2.0f);
    CGContextScaleCTM(targetContext, 1.0f, -1.0f);
    CGContextTranslateCTM(targetContext, -targetContextSize.width / 2.0f, -targetContextSize.height / 2.0f);
    [source drawInRect:CGRectMake(-inset, -inset, targetContextSize.width + inset * 2, targetContextSize.height + inset * 2) blendMode:kCGBlendModeCopy alpha:1.0f];
    UIGraphicsPopContext();
    
    if (averageColor != NULL)
    {
        *averageColor = TGImageAverageColor(targetMemory, targetContextSize.width, targetContextSize.height, (int)targetBytesPerRow);
    }
    
    TGStaticBackdropAreaData *timestampBackdropArea = createTimestampBackdropArea(targetMemory, targetContextSize.width, targetContextSize.height, (int)targetBytesPerRow, CGSizeMake(size.width, size.height));
    TGStaticBackdropAreaData *additionalDataBackdropArea = createAdditionalDataBackdropArea(targetMemory, targetContextSize.width, targetContextSize.height, (int)targetBytesPerRow, CGSizeMake(size.width, size.height));
    
    if (attachmentBorder)
    {
        addAttachmentImageCorners(targetMemory, targetContextSize.width, targetContextSize.height, (int)targetBytesPerRow, position, 1.0f);
    }
    else
    {
        if (cornerRadius > 0) {
            TGAddImageCorners(targetMemory, targetContextSize.width, targetContextSize.height, (int)targetBytesPerRow, cornerRadius, position);
        }
    }
    
    CGImageRef bitmapImage = CGBitmapContextCreateImage(targetContext);
    UIImage *image = [[UIImage alloc] initWithCGImage:bitmapImage scale:scale orientation:UIImageOrientationUp];
    CGImageRelease(bitmapImage);
    
    CGContextRelease(targetContext);
    free(targetMemory);
    
    TGStaticBackdropImageData *backdropData = [[TGStaticBackdropImageData alloc] init];
    [backdropData setBackdropArea:[[TGStaticBackdropAreaData alloc] initWithBackground:actionCircleImage] forKey:TGStaticBackdropMessageActionCircle];
    
    [backdropData setBackdropArea:timestampBackdropArea forKey:TGStaticBackdropMessageTimestamp];
    [backdropData setBackdropArea:additionalDataBackdropArea forKey:TGStaticBackdropMessageAdditionalData];
    
    [image setStaticBackdropImageData:backdropData];
    
    return image;
}

UIImage *TGAnimationFrameAttachmentImage(UIImage *source, CGSize size, CGSize renderSize)
{
    CGFloat scale = TGIsRetina() ? 2.0f : 1.0f;
    
    renderSize.width *= scale;
    renderSize.height *= scale;
    
    const struct { int width, height; } targetContextSize = { (int)(size.width * scale), (int)(size.height * scale) };
    
    size_t targetBytesPerRow = ((4 * (int)targetContextSize.width) + 15) & (~15);
    
    void *targetMemory = malloc((int)(targetBytesPerRow * targetContextSize.height));
    
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    CGBitmapInfo bitmapInfo = kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host;
    
    CGContextRef targetContext = CGBitmapContextCreate(targetMemory, (int)targetContextSize.width, (int)targetContextSize.height, 8, targetBytesPerRow, colorSpace, bitmapInfo);
    
    CGColorSpaceRelease(colorSpace);
    
    UIGraphicsPushContext(targetContext);
    CGContextTranslateCTM(targetContext, targetContextSize.width / 2.0f, targetContextSize.height / 2.0f);
    CGContextScaleCTM(targetContext, 1.0f, -1.0f);
    CGContextTranslateCTM(targetContext, -targetContextSize.width / 2.0f, -targetContextSize.height / 2.0f);
    
    CGContextSetFillColorWithColor(targetContext, [UIColor blackColor].CGColor);
    CGContextFillRect(targetContext, CGRectMake(0, 0, targetContextSize.width, targetContextSize.height));
    CGRect imageRect = CGRectMake((targetContextSize.width - renderSize.width) / 2.0f, (targetContextSize.height - renderSize.height) / 2.0f, renderSize.width, renderSize.height);
    [source drawInRect:imageRect blendMode:kCGBlendModeNormal alpha:1.0f];
    UIGraphicsPopContext();
    
    addAttachmentImageCorners(targetMemory, targetContextSize.width, targetContextSize.height, (int)targetBytesPerRow, 0, 1.0f);
    
    CGImageRef bitmapImage = CGBitmapContextCreateImage(targetContext);
    UIImage *image = [[UIImage alloc] initWithCGImage:bitmapImage scale:scale orientation:UIImageOrientationUp];
    CGImageRelease(bitmapImage);
    
    CGContextRelease(targetContext);
    free(targetMemory);
    
    return image;
}

UIImage *TGLoadedFileImage(UIImage *source, CGSize size, uint32_t *averageColor, int borderRadius)
{
    CGFloat scale = TGScreenScaling(); //TGIsRetina() ? 2.0f : 1.0f;
    
    CGFloat actionCircleDiameter = 50.0f;
    
    const struct { int width, height; } targetContextSize = { (int)(size.width * scale), (int)(size.height * scale) };
    const struct { int width, height; } actionCircleContextSize = { (int)(actionCircleDiameter * scale), (int)(actionCircleDiameter * scale) };
    
    size_t targetBytesPerRow = ((4 * (int)targetContextSize.width) + 15) & (~15);
    size_t actionCircleBytesPerRow = ((4 * (int)actionCircleContextSize.width) + 15) & (~15);
    
    void *targetMemory = malloc((int)(targetBytesPerRow * targetContextSize.height));
    memset(targetMemory, 0xff, targetBytesPerRow * targetContextSize.height);
    void *actionCircleMemory = malloc(((int)(actionCircleBytesPerRow * actionCircleContextSize.height)));
    memset(actionCircleMemory, 0xff, actionCircleBytesPerRow * actionCircleContextSize.height);
    
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    CGBitmapInfo bitmapInfo = kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host;
    
    CGContextRef targetContext = CGBitmapContextCreate(targetMemory, (int)targetContextSize.width, (int)targetContextSize.height, 8, targetBytesPerRow, colorSpace, bitmapInfo);
    CGContextRef actionCircleContext = CGBitmapContextCreate(actionCircleMemory, (int)actionCircleContextSize.width, (int)actionCircleContextSize.height, 8, actionCircleBytesPerRow, colorSpace, bitmapInfo);
    
    CGColorSpaceRelease(colorSpace);
    
    UIGraphicsPushContext(actionCircleContext);
    CGContextTranslateCTM(actionCircleContext, actionCircleContextSize.width / 2.0f, actionCircleContextSize.height / 2.0f);
    CGContextScaleCTM(actionCircleContext, 1.0f, -1.0f);
    CGContextTranslateCTM(actionCircleContext, -actionCircleContextSize.width / 2.0f, -actionCircleContextSize.height / 2.0f);
    CGContextSetInterpolationQuality(actionCircleContext, kCGInterpolationLow);
    CGContextSetBlendMode(actionCircleContext, kCGBlendModeCopy);
    
    [source drawInRect:CGRectMake((actionCircleContextSize.width - targetContextSize.width) / 2.0f, (actionCircleContextSize.height - targetContextSize.height) / 2.0f, targetContextSize.width, targetContextSize.height) blendMode:kCGBlendModeCopy alpha:1.0f];
    brightenAndBlurImage(actionCircleMemory, actionCircleContextSize.width, actionCircleContextSize.height, (int)actionCircleBytesPerRow, true);
    
    CGContextBeginPath(actionCircleContext);
    CGContextAddRect(actionCircleContext, CGRectMake(0.0f, 0.0f, actionCircleContextSize.width, actionCircleContextSize.height));
    CGContextAddEllipseInRect(actionCircleContext, CGRectMake(0.0f, 0.0f, actionCircleContextSize.width, actionCircleContextSize.height));
    CGContextClosePath(actionCircleContext);
    
    CGContextSetFillColorWithColor(actionCircleContext, [UIColor clearColor].CGColor);
    CGContextEOFillPath(actionCircleContext);
    
    UIGraphicsPopContext();
    
    CGImageRef actionCircleBitmapImage = CGBitmapContextCreateImage(actionCircleContext);
    UIImage *actionCircleImage = [[UIImage alloc] initWithCGImage:actionCircleBitmapImage scale:scale orientation:UIImageOrientationUp];
    CGImageRelease(actionCircleBitmapImage);
    
    CGContextRelease(actionCircleContext);
    free(actionCircleMemory);
    
    UIGraphicsPushContext(targetContext);
    CGContextTranslateCTM(targetContext, targetContextSize.width / 2.0f, targetContextSize.height / 2.0f);
    CGContextScaleCTM(targetContext, 1.0f, -1.0f);
    CGContextTranslateCTM(targetContext, -targetContextSize.width / 2.0f, -targetContextSize.height / 2.0f);
    [source drawInRect:CGRectMake(0, 0, targetContextSize.width, targetContextSize.height) blendMode:kCGBlendModeCopy alpha:1.0f];
    UIGraphicsPopContext();
    
    if (averageColor != NULL)
        *averageColor = TGImageAverageColor(targetMemory, targetContextSize.width, targetContextSize.height, (int)targetBytesPerRow);
    
    if (borderRadius != 0)
    {
        TGAddImageCorners(targetMemory, targetContextSize.width, targetContextSize.height, (int)targetBytesPerRow, (int)(borderRadius), 0);
    }
    
    TGStaticBackdropAreaData *timestampBackdropArea = createTimestampBackdropArea(targetMemory, targetContextSize.width, targetContextSize.height, (int)targetBytesPerRow, CGSizeMake(size.width, size.height));
    TGStaticBackdropAreaData *additionalDataBackdropArea = createAdditionalDataBackdropArea(targetMemory, targetContextSize.width, targetContextSize.height, (int)targetBytesPerRow, CGSizeMake(size.width, size.height));
    
    CGImageRef bitmapImage = CGBitmapContextCreateImage(targetContext);
    UIImage *image = [[UIImage alloc] initWithCGImage:bitmapImage scale:scale orientation:UIImageOrientationUp];
    CGImageRelease(bitmapImage);
    
    CGContextRelease(targetContext);
    free(targetMemory);
    
    TGStaticBackdropImageData *backdropData = [[TGStaticBackdropImageData alloc] init];
    [backdropData setBackdropArea:[[TGStaticBackdropAreaData alloc] initWithBackground:actionCircleImage] forKey:TGStaticBackdropMessageActionCircle];
    
    [backdropData setBackdropArea:timestampBackdropArea forKey:TGStaticBackdropMessageTimestamp];
    [backdropData setBackdropArea:additionalDataBackdropArea forKey:TGStaticBackdropMessageAdditionalData];
    
    [image setStaticBackdropImageData:backdropData];
    
    return image;
}

UIImage *TGReducedAttachmentImage(UIImage *source, CGSize originalSize, bool attachmentBorder, int position)
{
    return TGReducedAttachmentWithCornerRadiusImage(source, originalSize, attachmentBorder, attachmentBorder ? 14 : 15, position);
}

UIImage *TGReducedAttachmentWithCornerRadiusImage(UIImage *source, CGSize originalSize, bool attachmentBorder, int cornerRadius, int position)
{
    CGFloat scale = TGScreenScaling(); //TGIsRetina() ? 2.0f : 1.0f;
    
    CGSize size = CGSizeMake(CGFloor(originalSize.width * 0.4f), CGFloor(originalSize.height * 0.4f));
    cornerRadius = CGFloor(cornerRadius * 0.4f);
    
    const struct { int width, height; } targetContextSize = { (int)(size.width * scale), (int)(size.height * scale) };
    const struct { int width, height; } targetContextOriginalSize = { (int)(originalSize.width * scale), (int)(originalSize.height * scale) };
    
    CGFloat padding = 32.0f;
    CGFloat scaledWidth = targetContextOriginalSize.width / ((targetContextOriginalSize.width - padding * 2.0f) / (targetContextSize.width - padding * 2.0f));
    CGFloat scaledHeight = targetContextOriginalSize.height / ((targetContextOriginalSize.height - padding * 2.0f) / (targetContextSize.height - padding * 2.0f));
    
    size_t targetBytesPerRow = ((4 * (int)targetContextSize.width) + 15) & (~15);
    
    void *targetMemory = malloc((int)(targetBytesPerRow * targetContextSize.height));
    
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    CGBitmapInfo bitmapInfo = kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host;
    CGContextRef targetContext = CGBitmapContextCreate(targetMemory, (int)targetContextSize.width, (int)targetContextSize.height, 8, targetBytesPerRow, colorSpace, bitmapInfo);
    CGColorSpaceRelease(colorSpace);
    
    UIGraphicsPushContext(targetContext);
    
    CGContextSetInterpolationQuality(targetContext, kCGInterpolationMedium);
    
    CGContextSetFillColorWithColor(targetContext, [UIColor grayColor].CGColor);
    CGContextFillRect(targetContext, CGRectMake(0.0f, 0.0, targetContextSize.width, targetContextSize.height));
    
    CGContextTranslateCTM(targetContext, targetContextSize.width / 2.0f, targetContextSize.height / 2.0f);
    CGContextScaleCTM(targetContext, 1.0f, -1.0f);
    CGContextTranslateCTM(targetContext, -targetContextSize.width / 2.0f, -targetContextSize.height / 2.0f);
    
    CGContextSaveGState(targetContext);
    CGContextClipToRect(targetContext, CGRectMake(0.0f, 0.0f, padding, padding));
    [source drawInRect:CGRectMake(0, 0, targetContextOriginalSize.width, targetContextOriginalSize.height) blendMode:kCGBlendModeCopy alpha:1.0f];
    CGContextRestoreGState(targetContext);

    CGContextSaveGState(targetContext);
    CGContextClipToRect(targetContext, CGRectMake(targetContextSize.width - padding, 0.0f, padding, padding));
    [source drawInRect:CGRectMake(targetContextSize.width - targetContextOriginalSize.width, 0, targetContextOriginalSize.width, targetContextOriginalSize.height) blendMode:kCGBlendModeCopy alpha:1.0f];
    CGContextRestoreGState(targetContext);
    
    CGContextSaveGState(targetContext);
    CGContextClipToRect(targetContext, CGRectMake(0.0f, targetContextSize.height - padding, padding, padding));
    [source drawInRect:CGRectMake(0, targetContextSize.height - targetContextOriginalSize.height, targetContextOriginalSize.width, targetContextOriginalSize.height) blendMode:kCGBlendModeCopy alpha:1.0f];
    CGContextRestoreGState(targetContext);
    
    CGContextSaveGState(targetContext);
    CGContextClipToRect(targetContext, CGRectMake(targetContextSize.width - padding, targetContextSize.height - padding, padding, padding));
    [source drawInRect:CGRectMake(targetContextSize.width - targetContextOriginalSize.width, targetContextSize.height - targetContextOriginalSize.height, targetContextOriginalSize.width, targetContextOriginalSize.height) blendMode:kCGBlendModeCopy alpha:1.0f];
    CGContextRestoreGState(targetContext);
    
    CGContextSaveGState(targetContext);
    CGContextClipToRect(targetContext, CGRectMake(padding, 0.0f, targetContextSize.width - padding * 2, padding));
    [source drawInRect:CGRectMake((targetContextSize.width - scaledWidth) / 2.0f, 0, scaledWidth, targetContextOriginalSize.height) blendMode:kCGBlendModeCopy alpha:1.0f];
    CGContextRestoreGState(targetContext);
    
    CGContextSaveGState(targetContext);
    CGContextClipToRect(targetContext, CGRectMake(padding, targetContextSize.height - padding, targetContextSize.width - padding * 2, padding));
    [source drawInRect:CGRectMake((targetContextSize.width - scaledWidth) / 2.0f, targetContextSize.height - targetContextOriginalSize.height, scaledWidth, targetContextOriginalSize.height) blendMode:kCGBlendModeCopy alpha:1.0f];
    CGContextRestoreGState(targetContext);
    
    CGContextSaveGState(targetContext);
    CGContextClipToRect(targetContext, CGRectMake(0.0f, padding, padding, targetContextSize.height - padding * 2));
    [source drawInRect:CGRectMake(0, (targetContextSize.height - scaledHeight) / 2.0f, targetContextOriginalSize.width, scaledHeight) blendMode:kCGBlendModeCopy alpha:1.0f];
    CGContextRestoreGState(targetContext);
    
    CGContextSaveGState(targetContext);
    CGContextClipToRect(targetContext, CGRectMake(targetContextSize.width - padding, padding, padding, targetContextSize.height - padding * 2));
    [source drawInRect:CGRectMake(targetContextSize.width - targetContextOriginalSize.width, (targetContextSize.height - scaledHeight) / 2.0f, targetContextOriginalSize.width, scaledHeight) blendMode:kCGBlendModeCopy alpha:1.0f];
    CGContextRestoreGState(targetContext);
    
    CGContextClipToRect(targetContext, CGRectMake(padding, padding, targetContextSize.width - padding * 2, targetContextSize.height - padding * 2));
    [source drawInRect:CGRectMake((targetContextSize.width - scaledWidth) / 2.0f, (targetContextSize.height - scaledHeight) / 2.0f, scaledWidth, scaledHeight) blendMode:kCGBlendModeCopy alpha:1.0f];
    
    UIGraphicsPopContext();
    
//    if (attachmentBorder)
//        addAttachmentImageCorners(targetMemory, targetContextSize.width, targetContextSize.height, (int)targetBytesPerRow, position, 0.4f);
//    else
//    {
//        TGAddImageCorners(targetMemory, targetContextSize.width, targetContextSize.height, (int)(int)targetBytesPerRow, cornerRadius, position);
//    }
    
    CGImageRef bitmapImage = CGBitmapContextCreateImage(targetContext);
    UIImage *image = [[UIImage alloc] initWithCGImage:bitmapImage scale:scale orientation:UIImageOrientationUp];
    CGImageRelease(bitmapImage);
    
    CGContextRelease(targetContext);
    free(targetMemory);
    
    //return image;
    return [image resizableImageWithCapInsets:UIEdgeInsetsMake(padding / scale, padding / scale, padding / scale, padding / scale) resizingMode:UIImageResizingModeStretch];
}

UIImage *TGBlurredBackgroundImage(UIImage *source, CGSize size)
{
    CGFloat scale = source.scale;
    
    const struct { int width, height; } targetContextSize = { (int)(size.width * scale), (int)(size.height * scale) };
    
    size_t targetBytesPerRow = ((4 * (int)targetContextSize.width) + 15) & (~15);
    
    void *targetMemory = malloc((int)(targetBytesPerRow * targetContextSize.height));
    
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    CGBitmapInfo bitmapInfo = kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host;
    
    CGContextRef targetContext = CGBitmapContextCreate(targetMemory, (int)targetContextSize.width, (int)targetContextSize.height, 8, targetBytesPerRow, colorSpace, bitmapInfo);
    
    CGColorSpaceRelease(colorSpace);
    
    UIGraphicsPushContext(targetContext);
    CGContextTranslateCTM(targetContext, targetContextSize.width / 2.0f, targetContextSize.height / 2.0f);
    CGContextScaleCTM(targetContext, 1.0f, -1.0f);
    CGContextTranslateCTM(targetContext, -targetContextSize.width / 2.0f, -targetContextSize.height / 2.0f);
    [source drawInRect:CGRectMake(0, 0, targetContextSize.width, targetContextSize.height) blendMode:kCGBlendModeCopy alpha:1.0f];
    UIGraphicsPopContext();
    
    brightenAndBlurImage(targetMemory, targetContextSize.width, targetContextSize.height, (int)targetBytesPerRow, false);
    
    CGImageRef bitmapImage = CGBitmapContextCreateImage(targetContext);
    UIImage *image = [[UIImage alloc] initWithCGImage:bitmapImage scale:scale orientation:UIImageOrientationUp];
    CGImageRelease(bitmapImage);
    
    CGContextRelease(targetContext);
    free(targetMemory);
    
    return image;
}

UIImage *TGRoundImage(UIImage *source, CGSize size)
{
    CGFloat scale = TGIsRetina() ? 2.0f : 1.0f;
    
    const struct { int width, height; } targetContextSize = { (int)(size.width * scale), (int)(size.height * scale) };
    size_t targetBytesPerRow = ((4 * (int)targetContextSize.width) + 15) & (~15);
    
    void *targetMemory = malloc((int)(targetBytesPerRow * targetContextSize.height));
    memset(targetMemory, 0, (int)(targetBytesPerRow * targetContextSize.height));
    
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    CGBitmapInfo bitmapInfo = kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host;
    
    CGContextRef targetContext = CGBitmapContextCreate(targetMemory, (int)targetContextSize.width, (int)targetContextSize.height, 8, targetBytesPerRow, colorSpace, bitmapInfo);
    
    CGColorSpaceRelease(colorSpace);
    
    UIGraphicsPushContext(targetContext);
    CGContextTranslateCTM(targetContext, targetContextSize.width / 2.0f, targetContextSize.height / 2.0f);
    CGContextScaleCTM(targetContext, 1.0f, -1.0f);
    CGContextTranslateCTM(targetContext, -targetContextSize.width / 2.0f, -targetContextSize.height / 2.0f);
    
    CGContextBeginPath(targetContext);
    CGContextAddEllipseInRect(targetContext, CGRectMake(0.0f, 0.0f, targetContextSize.width, targetContextSize.height));
    CGContextClip(targetContext);
    
    [source drawInRect:CGRectMake(0, 0, targetContextSize.width, targetContextSize.height) blendMode:kCGBlendModeCopy alpha:1.0f];
    UIGraphicsPopContext();
    
    CGImageRef bitmapImage = CGBitmapContextCreateImage(targetContext);
    UIImage *image = [[UIImage alloc] initWithCGImage:bitmapImage scale:scale orientation:UIImageOrientationUp];
    CGImageRelease(bitmapImage);
    
    CGContextRelease(targetContext);
    free(targetMemory);
    
    return image;
}

void TGPlainImageAverageColor(UIImage *source, uint32_t *averageColor)
{
    CGFloat scale = source.scale;
    CGSize size = source.size;
    
    const struct { int width, height; } targetContextSize = { (int)(size.width * scale), (int)(size.height * scale) };
    
    size_t targetBytesPerRow = ((4 * (int)targetContextSize.width) + 15) & (~15);
    
    void *targetMemory = malloc((int)(targetBytesPerRow * targetContextSize.height));
    
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    CGBitmapInfo bitmapInfo = kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host;
    
    CGContextRef targetContext = CGBitmapContextCreate(targetMemory, (int)targetContextSize.width, (int)targetContextSize.height, 8, targetBytesPerRow, colorSpace, bitmapInfo);
    
    CGColorSpaceRelease(colorSpace);
    
    UIGraphicsPushContext(targetContext);
    CGContextTranslateCTM(targetContext, targetContextSize.width / 2.0f, targetContextSize.height / 2.0f);
    CGContextScaleCTM(targetContext, 1.0f, -1.0f);
    CGContextTranslateCTM(targetContext, -targetContextSize.width / 2.0f, -targetContextSize.height / 2.0f);
    [source drawInRect:CGRectMake(0, 0, targetContextSize.width, targetContextSize.height) blendMode:kCGBlendModeCopy alpha:1.0f];
    UIGraphicsPopContext();
    
    if (averageColor != NULL)
        *averageColor = TGImageAverageColor(targetMemory, targetContextSize.width, targetContextSize.height, (int)targetBytesPerRow);
    
    CGContextRelease(targetContext);
    free(targetMemory);
}

static void lightBlurImage(void *pixels, unsigned int width, unsigned int height, unsigned int stride)
{
    unsigned int tempWidth = width / 6;
    unsigned int tempHeight = height / 6;
    unsigned int tempStride = ((4 * tempWidth + 15) & (~15));
    void *tempPixels = malloc(tempStride * tempHeight);
    
    vImage_Buffer srcBuffer;
    srcBuffer.width = width;
    srcBuffer.height = height;
    srcBuffer.rowBytes = stride;
    srcBuffer.data = pixels;
    
    vImage_Buffer dstBuffer;
    dstBuffer.width = tempWidth;
    dstBuffer.height = tempHeight;
    dstBuffer.rowBytes = tempStride;
    dstBuffer.data = tempPixels;
    
    vImageScale_ARGB8888(&srcBuffer, &dstBuffer, NULL, kvImageDoNotTile);
    
    fastBlur(tempWidth, tempHeight, tempStride, tempPixels);
    
    vImageScale_ARGB8888(&dstBuffer, &srcBuffer, NULL, kvImageDoNotTile);
    
    free(tempPixels);
}

UIImage *TGCropBackdropImage(UIImage *source, CGSize size)
{
    CGFloat scale = source.scale;
    
    const struct { int width, height; } targetContextSize = { (int)(size.width * scale), (int)(size.height * scale) };
    
    size_t targetBytesPerRow = ((4 * (int)targetContextSize.width) + 15) & (~15);
    
    void *targetMemory = malloc((int)(targetBytesPerRow * targetContextSize.height));
    
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    CGBitmapInfo bitmapInfo = kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host;
    
    CGContextRef targetContext = CGBitmapContextCreate(targetMemory, (int)targetContextSize.width, (int)targetContextSize.height, 8, targetBytesPerRow, colorSpace, bitmapInfo);
    
    CGColorSpaceRelease(colorSpace);
    
    UIGraphicsPushContext(targetContext);
    CGContextTranslateCTM(targetContext, targetContextSize.width / 2.0f, targetContextSize.height / 2.0f);
    CGContextScaleCTM(targetContext, 1.0f, -1.0f);
    CGContextTranslateCTM(targetContext, -targetContextSize.width / 2.0f, -targetContextSize.height / 2.0f);
    CGContextSetFillColorWithColor(targetContext, [UIColor blackColor].CGColor);
    CGContextFillRect(targetContext, CGRectMake(0, 0, targetContextSize.width, targetContextSize.height));
    
    CGSize halfSize = CGSizeMake(CGFloor(size.width * scale / 2.0f), CGFloor(size.height * scale / 2.0f));
    [source drawInRect:CGRectMake((targetContextSize.width - halfSize.width) / 2, (targetContextSize.height - halfSize.height) / 2, halfSize.width, halfSize.height) blendMode:kCGBlendModeNormal alpha:0.6f];
    UIGraphicsPopContext();

    lightBlurImage(targetMemory, targetContextSize.width, targetContextSize.height, (int)targetBytesPerRow);
    
    CGImageRef bitmapImage = CGBitmapContextCreateImage(targetContext);
    UIImage *image = [[UIImage alloc] initWithCGImage:bitmapImage scale:scale orientation:UIImageOrientationUp];
    CGImageRelease(bitmapImage);
    
    CGContextRelease(targetContext);
    free(targetMemory);
    
    return image;
}

UIImage *TGCameraPositionSwitchImage(UIImage *source, CGSize size)
{
    return TGBlurredRectangularImage(source, true, size, size, NULL, nil);
}

UIImage *TGCameraModeSwitchImage(UIImage *source, CGSize size)
{
    return TGBlurredRectangularImage(source, true, size, size, NULL, nil);
}

UIImage *TGScaleAndCropImageToPixelSize(UIImage *source, CGSize size, CGSize renderSize, uint32_t *averageColor, void (^pixelProcessingBlock)(void *, int, int, int))
{
    const struct { int width, height; } targetContextSize = { (int)(size.width), (int)(size.height)};
    
    size_t targetBytesPerRow = ((4 * (int)targetContextSize.width) + 15) & (~15);
    
    void *targetMemory = malloc((int)(targetBytesPerRow * targetContextSize.height));
    
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    CGBitmapInfo bitmapInfo = kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host;
    
    CGContextRef targetContext = CGBitmapContextCreate(targetMemory, (int)targetContextSize.width, (int)targetContextSize.height, 8, targetBytesPerRow, colorSpace, bitmapInfo);
    
    CGColorSpaceRelease(colorSpace);
    
    UIGraphicsPushContext(targetContext);
    
    CGContextTranslateCTM(targetContext, targetContextSize.width / 2.0f, targetContextSize.height / 2.0f);
    CGContextScaleCTM(targetContext, 1.0f, -1.0f);
    CGContextTranslateCTM(targetContext, -targetContextSize.width / 2.0f, -targetContextSize.height / 2.0f);
    CGContextSetInterpolationQuality(targetContext, kCGInterpolationMedium);
    [source drawInRect:CGRectMake((size.width - renderSize.width) / 2.0f, (size.height - renderSize.height) / 2.0f, renderSize.width, renderSize.height) blendMode:kCGBlendModeCopy alpha:1.0f];
    UIGraphicsPopContext();
    
    if (averageColor != NULL)
    {
        *averageColor = TGImageAverageColor(targetMemory, targetContextSize.width, targetContextSize.height, (int)targetBytesPerRow);
    }
    
    if (pixelProcessingBlock)
        pixelProcessingBlock(targetMemory, targetContextSize.width, targetContextSize.height, (int)targetBytesPerRow);
    
    CGImageRef bitmapImage = CGBitmapContextCreateImage(targetContext);
    UIImage *image = [[UIImage alloc] initWithCGImage:bitmapImage];
    CGImageRelease(bitmapImage);
    
    CGContextRelease(targetContext);
    free(targetMemory);
    
    return image;
}

NSArray *TGBlurredBackgroundImages(UIImage *source, CGSize sourceSize)
{
    CGSize size = TGFitSize(sourceSize, CGSizeMake(220, 220));
    CGSize renderSize = size;
    
    const struct { int width, height; } targetContextSize = { (int)(size.width), (int)(size.height)};
    
    size_t targetBytesPerRow = ((4 * (int)targetContextSize.width) + 15) & (~15);
    
    void *targetMemory = malloc((int)(targetBytesPerRow * targetContextSize.height));
    
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    CGBitmapInfo bitmapInfo = kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host;
    
    CGContextRef targetContext = CGBitmapContextCreate(targetMemory, (int)targetContextSize.width, (int)targetContextSize.height, 8, targetBytesPerRow, colorSpace, bitmapInfo);
    
    CGColorSpaceRelease(colorSpace);
    
    UIGraphicsPushContext(targetContext);
    
    CGContextTranslateCTM(targetContext, targetContextSize.width / 2.0f, targetContextSize.height / 2.0f);
    CGContextScaleCTM(targetContext, 1.0f, -1.0f);
    CGContextTranslateCTM(targetContext, -targetContextSize.width / 2.0f, -targetContextSize.height / 2.0f);
    CGContextSetInterpolationQuality(targetContext, kCGInterpolationMedium);
    
    [source drawInRect:CGRectMake((size.width - renderSize.width) / 2.0f, (size.height - renderSize.height) / 2.0f, renderSize.width, renderSize.height) blendMode:kCGBlendModeCopy alpha:1.0f];
    
    fastBlurMore(targetContextSize.width, targetContextSize.height, (int)targetBytesPerRow, targetMemory);
    fastBlurMore(targetContextSize.width, targetContextSize.height, (int)targetBytesPerRow, targetMemory);
    
    brightenImage(targetMemory, targetContextSize.width, targetContextSize.height, (int)targetBytesPerRow);
    CGContextSetFillColorWithColor(targetContext, [UIColor colorWithWhite:1.0f alpha:0.15f].CGColor);
    CGContextFillRect(targetContext, CGRectMake(0.0f, 0.0f, targetContextSize.width, targetContextSize.height));
    CGImageRef bitmapImage = CGBitmapContextCreateImage(targetContext);
    UIImage *foregroundImage = [[UIImage alloc] initWithCGImage:bitmapImage];
    CGImageRelease(bitmapImage);
    
    [source drawInRect:CGRectMake((size.width - renderSize.width) / 2.0f, (size.height - renderSize.height) / 2.0f, renderSize.width, renderSize.height) blendMode:kCGBlendModeCopy alpha:1.0f];
    
    fastBlurMore(targetContextSize.width, targetContextSize.height, (int)targetBytesPerRow, targetMemory);
    fastBlurMore(targetContextSize.width, targetContextSize.height, (int)targetBytesPerRow, targetMemory);
    
    CGContextSetFillColorWithColor(targetContext, [UIColor colorWithWhite:0.0f alpha:0.5f].CGColor);
    CGContextFillRect(targetContext, CGRectMake(0.0f, 0.0f, targetContextSize.width, targetContextSize.height));
    
    bitmapImage = CGBitmapContextCreateImage(targetContext);
    UIImage *backgroundImage = [[UIImage alloc] initWithCGImage:bitmapImage];
    CGImageRelease(bitmapImage);
    
    UIGraphicsPopContext();
    
    CGContextRelease(targetContext);
    free(targetMemory);
    
    NSMutableArray *array = [[NSMutableArray alloc] init];
    [array addObject:backgroundImage];
    [array addObject:foregroundImage];
    return array;
}

void telegramFastBlur(int imageWidth, int imageHeight, int imageStride, void *pixels)
{
    uint8_t *pix = (uint8_t *)pixels;
    const int w = imageWidth;
    const int h = imageHeight;
    const int stride = imageStride;
    const int radius = 3;
    const int r1 = radius + 1;
    const int div = radius * 2 + 1;
    
    if (radius > 15 || div >= w || div >= h)
    {
        return;
    }
    
    uint64_t *rgb = malloc(imageStride * imageHeight * sizeof(uint64_t));
    
    int x, y, i;
    
    int yw = 0;
    const int we = w - r1;
    for (y = 0; y < h; y++) {
        uint64_t cur = get_colors (&pix[yw]);
        uint64_t rgballsum = -radius * cur;
        uint64_t rgbsum = cur * ((r1 * (r1 + 1)) >> 1);
        
        for (i = 1; i <= radius; i++) {
            uint64_t cur = get_colors (&pix[yw + i * 4]);
            rgbsum += cur * (r1 - i);
            rgballsum += cur;
        }
        
        x = 0;
        
#define update(start, middle, end)                         \
rgb[y * w + x] = (rgbsum >> 4) & 0x00FF00FF00FF00FF; \
\
rgballsum += get_colors (&pix[yw + (start) * 4]) -   \
2 * get_colors (&pix[yw + (middle) * 4]) +  \
get_colors (&pix[yw + (end) * 4]);      \
rgbsum += rgballsum;                                 \
x++;                                                 \

        while (x < r1) {
            update (0, x, x + r1);
        }
        while (x < we) {
            update (x - r1, x, x + r1);
        }
        while (x < w) {
            update (x - r1, x, w - 1);
        }
#undef update
        
        yw += stride;
    }
    
    const int he = h - r1;
    for (x = 0; x < w; x++) {
        uint64_t rgballsum = -radius * rgb[x];
        uint64_t rgbsum = rgb[x] * ((r1 * (r1 + 1)) >> 1);
        for (i = 1; i <= radius; i++) {
            rgbsum += rgb[i * w + x] * (r1 - i);
            rgballsum += rgb[i * w + x];
        }
        
        y = 0;
        int yi = x * 4;
        
#define update(start, middle, end)         \
int64_t res = rgbsum >> 4;           \
pix[yi] = (uint8_t)res;                       \
pix[yi + 1] = (uint8_t)(res >> 16);             \
pix[yi + 2] = (uint8_t)(res >> 32);             \
\
rgballsum += rgb[x + (start) * w] -  \
2 * rgb[x + (middle) * w] + \
rgb[x + (end) * w];     \
rgbsum += rgballsum;                 \
y++;                                 \
yi += stride;
        
        while (y < r1) {
            update (0, y, y + r1);
        }
        while (y < he) {
            update (y - r1, y, y + r1);
        }
        while (y < h) {
            update (y - r1, y, h - 1);
        }
#undef update
    }
    
    free(rgb);
}