/*
 * Part of Picozu Image Editing application.
 * Visit https://www.picozu.com for more info.
 * The code is copyrighted by Picozu <www.picozu.com>.
 
 * @version 1.0.012316
 * @package Core Application
 * @application Picozu Mobile <www.picozu.com/mobile>
 * @license www.picozu.com/license
 */

var PicozuExpress = function () {
    this.FRAMES = 17;
    this.BACKGROUNDS = 12;
    this.TOOLS = ['filters', 'brightness', 'contrast', 'saturation', 'noise', 'vignette', 'effects'];
    this.SETTINGS = ['rotation', 'flip', 'brightness', 'contrast', 'saturation', 'noise', 'sharpen', 'blur', 'frameblur', 'vignette', 'effects', 'rgb', 'crop', 'frames', 'backgrounds'];
    this.canvas = null;
    this.context = null;
    this.tempCanvas = null;
    this.tempContext = null;
    this.checkPoint = null;
    this.image = null;
    this.video = null;
    this.videoWidth = null;
    this.videoHeight = null;
    this.streaming = null;
    this.streamInterval = null;
    this.isStreaming = false;
    this.currentFilter = "Original";
    this.brightnessValue = 0;
    this.contrastValue = 0;
    this.saturationValue = 1;
    this.noiseValue = 0;
    this.vignette = false;
    this.effect = 'none';
    this.degrees = 0;
    this.blurRadius = 32;
    this.radiusFactor = 1.5;
    this.divider = this.radiusFactor;
    this.startRadius = this.blurRadius / this.divider;
    this.steps = 3;
    this.index = [];
    this.selection = ["Original", "Charming", "Gold Orange", "Hot Desert", "Lord Kelvin", "1977", "Beautiful", "Milky", "Summertime", "Excited", "Cold Tibet", "Sunday", "California", "Paris in October", "August March", "Nashville", "Brannan", "Blood Orange", "Hefe", "Gotham", "X-PRO II", "Forest", "Moonlight", "Angelic"];
    this.mul_table = [512, 512, 456, 512, 328, 456, 335, 512, 405, 328, 271, 456, 388, 335, 292, 512, 454, 405, 364, 328, 298, 271, 496, 456, 420, 388, 360, 335, 312, 292, 273, 512, 482, 454, 428, 405, 383, 364, 345, 328, 312, 298, 284, 271, 259, 496, 475, 456, 437, 420, 404, 388, 374, 360, 347, 335, 323, 312, 302, 292, 282, 273, 265, 512, 497, 482, 468, 454, 441, 428, 417, 405, 394, 383, 373, 364, 354, 345, 337, 328, 320, 312, 305, 298, 291, 284, 278, 271, 265, 259, 507, 496, 485, 475, 465, 456, 446, 437, 428, 420, 412, 404, 396, 388, 381, 374, 367, 360, 354, 347, 341, 335, 329, 323, 318, 312, 307, 302, 297, 292, 287, 282, 278, 273, 269, 265, 261, 512, 505, 497, 489, 482, 475, 468, 461, 454, 447, 441, 435, 428, 422, 417, 411, 405, 399, 394, 389, 383, 378, 373, 368, 364, 359, 354, 350, 345, 341, 337, 332, 328, 324, 320, 316, 312, 309, 305, 301, 298, 294, 291, 287, 284, 281, 278, 274, 271, 268, 265, 262, 259, 257, 507, 501, 496, 491, 485, 480, 475, 470, 465, 460, 456, 451, 446, 442, 437, 433, 428, 424, 420, 416, 412, 408, 404, 400, 396, 392, 388, 385, 381, 377, 374, 370, 367, 363, 360, 357, 354, 350, 347, 344, 341, 338, 335, 332, 329, 326, 323, 320, 318, 315, 312, 310, 307, 304, 302, 299, 297, 294, 292, 289, 287, 285, 282, 280, 278, 275, 273, 271, 269, 267, 265, 263, 261, 259];
    this.shg_table = [9, 11, 12, 13, 13, 14, 14, 15, 15, 15, 15, 16, 16, 16, 16, 17, 17, 17, 17, 17, 17, 17, 18, 18, 18, 18, 18, 18, 18, 18, 18, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24];
    this.FILTERS = null;

    this.__constructor = function () {
        var self = this;
        this.FILTERS = PicozuMobileFilters;
        PicozuMobileFilters = null;
        for (var x in this.FILTERS) {
            self.index.push(x);
        }
        var app = angular.module('picozu', ['ngAnimate']);
        app.controller('PackageController', function ($scope) {
            $scope.update = function () {
                $('.list-item').on('mouseenter', function () {
                    $(this).addClass('active');
                });
                $('.list-item').on('mouseleave', function () {
                    $(this).removeClass('active');
                });
            };
            $scope.applyFilter = function (filter) {
                self.applyFilter(filter);
            };
            $scope.filters = self.index;
            $scope.selection = self.selection;
        });
        $(document).ready(function () {
            self.buildUI();
            self.canvas = $('#pico-x-canvas')[0];
            self.image = $('#pico-x-initialImage')[0];
            var src = self.getUrlParams();
            if (src === null) {
                self.image.src = 'img/pictures/Default.jpg';
                self.image.onload = function () {
                    self.initCanvas();
                };
            } else {
                self.image.src = src;
                self.image.onload = function () {
                    self.initCanvas();
                };
            }
            self.video = $('#pico-x-video')[0];
            self.canvas.addEventListener('dragenter', self.dragenter, false);
            self.canvas.addEventListener('dragover', self.dragover, false);
            self.canvas.addEventListener('drop', self.drop, false);
            self.video.addEventListener('canplay', function (e) {
                if (!self.isStreaming) {
                    self.videoWidth = self.video.videoWidth;
                    self.videoHeight = self.video.videoHeight;
                    self.canvas.setAttribute('width', self.videoWidth);
                    self.canvas.setAttribute('height', self.videoHeight);
                    self.canvasPosition();
                    self.context.translate(self.videoWidth, 0);
                    self.context.scale(-1, 1);
                    $('.interaction').hide();
                    $('.pico-x-show-undo').hide();
                    $('#pico-x-show-savecheckpoint').hide();
                    $('.nav').hide();
                    $('.pico-x-camera-take').show();
                    $('#package-icon').show();
                    $('#pico-x-filters').addClass('toright');
                    self.showCSettings();
                    self.isStreaming = true;
                }
            }, false);
            self.video.addEventListener('play', function () {
                self.streamInterval = setInterval(function () {
                    if (self.video.paused || self.video.ended) {
                        return;
                    }
                    self.context.fillRect(0, 0, self.videoWidth, self.videoHeight);
                    self.context.drawImage(self.video, 0, 0, self.videoWidth, self.videoHeight);
                    self.applyFilter(self.currentFilter);
                    self.applyBrightness(self.brightnessValue);
                    self.applyContrast(self.contrastValue);
                    self.applySaturation(self.saturationValue);
                    self.applyNoise(self.noiseValue);
                    if (self.vignette) {
                        self.applyVignette();
                    }
                    if (self.effect === 'Grayscale' && self.effect !== 'none') {
                        self.applyGrayscale();
                    }
                    if (self.effect === 'Sepia' && self.effect !== 'none') {
                        self.applySepia();
                    }
                    if (self.effect === 'Invert' && self.effect !== 'none') {
                        self.applyInvert();
                    }
                }, 33);
            }, false);
            $('#pico-x-canvas').swipe({
                swipeLeft: function () {
                    if (self.selection.indexOf(self.currentFilter) < self.selection.length - 1) {
                        self.currentFilter = self.selection[self.selection.indexOf(self.currentFilter) + 1];
                        self.applyFilter(self.currentFilter);
                    } else if (self.selection.indexOf(self.currentFilter) == self.selection.length - 1) {
                        self.currentFilter = self.selection[0];
                        self.applyFilter(self.currentFilter);
                    }
                },
                swipeRight: function () {
                    if (self.selection.indexOf(self.currentFilter) > 0) {
                        self.currentFilter = self.selection[self.selection.indexOf(self.currentFilter) - 1];
                        self.applyFilter(self.currentFilter);
                    } else if (self.selection.indexOf(self.currentFilter) == 0) {
                        self.currentFilter = self.selection[self.selection.length - 1];
                        self.applyFilter(self.currentFilter);
                    }
                },
                threshold: 0
            });
            $('.setting').perfectScrollbar();
            $('.theme').perfectScrollbar();
            $('#filters-icon').addClass('active');
            $(window).on('resize orientationchange', function () {
                $('.setting').perfectScrollbar('update');
                self.canvasPosition();
            });
            $('.picozu-express').on('change', '.pico-x-do-upload', function () {
                self.getImage(this.files);
                return false;
            }).on('click', '.interaction', function () {
                $('.' + 'active').each(function () {
                    $(this).removeClass('active');
                });
                $(this).addClass('active');
                $('#back').hide();
            }).on('click', '.pico-x-import', function () {
                self.importImage();
                self.showEditor();
                return false;
            }).on('click', '.pico-x-upload', function () {
                self.upload();
                return false;
            }).on('click', '.back', function () {
                self.showSettings();
                return false;
            }).on('click', '#c-back', function () {
                self.showCSettings();
                return false;
            }).on('click', '.pico-x-show-filters', function () {
                self.showFilters();
                return false;
            }).on('click', '.pico-x-show-packages', function () {
                self.showPackage();
                return false;
            }).on('click', '.pico-x-show-settings', function () {
                self.showSettings();
                return false;
            }).on('click', '.pico-x-show-undo', function () {
                self.restoreUndo();
                return false;
            }).on('click', '#pico-x-show-savecheckpoint', function () {
                self.saveUndo();
                return false;
            }).on('click', '.pico-x-share', function () {
                var dt = self.canvas.toDataURL('image/jpeg');
                self.save(dt);
                return false;
            }).on('click', '.pico-x-userver', function () {
                var dt = self.canvas.toDataURL('image/jpeg');
                self.save(dt);
                return false;
            }).on('click', '.pico-x-export', function () {
                var dt = self.canvas.toDataURL('image/jpeg');
                $(this).attr('href', dt);
            }).on('click', '.pico-x-restore', function () {
                self.restore();
                return false;
            }).on('click', '.pico-x-camera', function () {
                self.getCamera();
                return false;
            }).on('click', '.pico-x-effects-1', function () {
                if (!self.isStreaming) {
                    self.restoreUndo();
                }
                self.effect = 'none';
                return false;
            }).on('click', '.pico-x-effects-2', function () {
                self.applyGrayscale();
                return false;
            }).on('click', '.pico-x-effects-3', function () {
                self.applySepia();
                return false;
            }).on('click', '.pico-x-effects-4', function () {
                self.applyInvert();
                return false;
            }).on('click', '.pico-x-crop-1', function () {
                self.applyCrop(0);
                return false;
            }).on('click', '.pico-x-crop-2', function () {
                self.applyCrop(12);
                return false;
            }).on('click', '.pico-x-crop-3', function () {
                self.applyCrop(10);
                return false;
            }).on('click', '.pico-x-crop-4', function () {
                self.applyCrop(8);
                return false;
            }).on('click', '.pico-x-crop-5', function () {
                self.applyCrop(6);
                return false;
            }).on('click', '.pico-x-crop-6', function () {
                self.applyCrop(4);
                return false;
            }).on('click', '.pico-x-rotation', function () {
                self.applyRotation();
                return false;
            }).on('click', '.pico-x-fliph', function () {
                self.applyFlipHorizontal();
                return false;
            }).on('change', '.pico-x-brightness', function () {
                self.applyBrightness($(this).val());
                return false;
            }).on('change', '.pico-x-contrast', function () {
                self.applyContrast($(this).val());
                return false;
            }).on('change', '.pico-x-saturation', function () {
                self.applySaturation($(this).val());
                return false;
            }).on('change', '.pico-x-noise', function () {
                self.applyNoise($(this).val());
                return false;
            }).on('change', '.pico-x-sharpen', function () {
                self.applySharpen($(this).val());
                return false;
            }).on('change', '.pico-x-blur', function () {
                self.effectBlurImage($(this).val(), true);
                return false;
            }).on('change', '.pico-x-frameblur', function () {
                self.applyFrameBlur($(this).val());
                return false;
            }).on('click', '.pico-x-vignette', function () {
                self.applyVignette();
                return false;
            }).on('change', '.pico-x-apply-r', function () {
                self.applyRed($(this).val());
                return false;
            }).on('change', '.pico-x-apply-g', function () {
                self.applyGreen($(this).val());
                return false;
            }).on('change', '.pico-x-apply-b', function () {
                self.applyBlue($(this).val());
                return false;
            });
            $("#loading").css('visibility', 'visible');
        });
        $(window).on("load", function () {
            $("html").css('visibility', 'visible');
            $("#loading").css('display', 'none');
        });
        return this;
    };

    this.__destructor = function () {
        // TODO
    };

    this.loadFilterPreviews = function () {
        for (var i = 0; i < this.selection.length; i++) {
            this.applyPreviewFilter(i);
        }
    };

    this.buildUI = function () {
        var self = this;
        var _t = '';
        for (var i = 0; i < self.TOOLS.length; i++) {
            _t += '<li class="item"><img data-tool="' + self.TOOLS[i] + '" src="img/gui/buttons/' + self.TOOLS[i] + '.png" class="pico-x-show-' + self.TOOLS[i] + ' circular" /></li>';
        }
        $('#c-settings').empty().append(_t);
        $('#c-settings').on('click', '.item > img', function () {
            var tool = $(this).data('tool');
            var func = 'show' + self.ucwords(tool);
            self[func]();
            $('.pico-x-show-undo').hide();
            $('#c-back').show();
            return false;
        });
        _t = '';
        for (var i = 0; i < self.SETTINGS.length; i++) {
            _t += '<li class="item"><img data-tool="' + self.SETTINGS[i] + '" src="img/gui/buttons/' + self.SETTINGS[i] + '.png" class="pico-x-show-' + self.SETTINGS[i] + ' circular" /></li>';
        }
        $('#settings').empty().append(_t);
        $('#settings').on('click', '.item > img', function () {
            var tool = $(this).data('tool');
            var func = 'show' + self.ucwords(tool);
            self[func]();
            return false;
        });
        _t = '';
        for (var i = 0; i < self.selection.length; i++) {
            _t += '<li class="item"><canvas width="50" height="50" class="circular canvas-filter-' + i + '" data-filter="' + self.selection[i] + '"></canvas></li>';
        }
        $('#pico-x-filters').empty().append(_t);
        $('#pico-x-filters').on('click', '.item > canvas', function () {
            var filter = $(this).data('filter');
            self.applyFilter(filter);
            return false;
        });
        _t = '';
        for (var i = 0; i < self.FRAMES - 1; i++) {
            _t += '<li class="item"><img data-frame="frame' + i + '" src="img/frames/frame' + i + '.jpg" class="square" /></li>';
        }
        $('#pico-x-frames').empty().append(_t);
        $('#pico-x-frames').on('click', '.item > img', function () {
            var frame = $(this).data('frame');
            if (frame === 'frame0') {
                self.restoreUndo();
            } else {
                self.applyFrame(frame);
            }
            return false;
        });
        _t = '';
        for (var i = 0; i < self.BACKGROUNDS - 1; i++) {
            _t += '<li class="item"><img data-background="background' + i + '" src="img/backgrounds/background' + i + '.jpg" class="circular" /></li>';
        }
        $('#pico-x-backgrounds').empty().append(_t);
        $('#pico-x-backgrounds').on('click', '.item > img', function () {
            var background = $(this).data('background');
            if (background === 'background0') {
                self.restoreUndo();
            } else {
                self.applyBackground(background);
            }
            return false;
        });
    };

    this.upload = function () {
        var imageUp = $('#imageUp')[0];
        if (imageUp) {
            imageUp.click();
        }
    };

    this.getImage = function (files) {
        var self = this;
        if (files.length) {
            var file = files[0];
            this.image.src = window.URL.createObjectURL(file);
            this.image.onload = function () {
                self.initCanvas();
            };
        }
    };

    this.dragenter = function (e) {
        e.stopPropagation();
        e.preventDefault();
    };

    this.dragover = function (e) {
        e.stopPropagation();
        e.preventDefault();
    };

    this.drop = function (e) {
        e.stopPropagation();
        e.preventDefault();
        var dt = e.dataTransfer;
        var files = dt.files;
        this.getImage(files);
    };

    this.applyFrameBlur = function (value) {
        var gradientPixels = this.getLinearGradientMap(this.canvas.width, this.canvas.height, this.canvas.width * 0.5, this.canvas.height * 0.5, -Math.PI * 0.1, this.canvas.width * 2, true);
        var outerRadius = Math.sqrt(Math.pow(this.canvas.width / 2, 2) + Math.pow(this.canvas.height / 2, 2));
        this.blurRadius = parseInt(value, 10);
        this.radiusFactor = 1.5;
        this.divider = this.radiusFactor;
        for (var i = 1; i < this.steps; i++) {
            this.divider += Math.pow(this.radiusFactor, i + 1);
        }
        this.startRadius = this.blurRadius / this.divider;
        this.compoundBlurImage(gradientPixels, this.startRadius, this.radiusFactor, this.steps, true);
    };

    this.effectBlurCanvasRGBA = function (top_x, top_y, radius) {
        if (isNaN(radius) || radius < 1)
            return;
        radius |= 0;
        var width = this.canvas.width;
        var height = this.canvas.height;
        var imageData = this.context.getImageData(0, 0, width, height);
        var pixels = imageData.data;
        var x, y, i, p, yp, yi, yw, r_sum, g_sum, b_sum, a_sum, r_out_sum, g_out_sum, b_out_sum, a_out_sum, r_in_sum, g_in_sum, b_in_sum, a_in_sum, pr, pg, pb, pa, rbs;
        var div = radius + radius + 1;
        var w4 = width << 2;
        var widthMinus1 = width - 1;
        var heightMinus1 = height - 1;
        var radiusPlus1 = radius + 1;
        var sumFactor = radiusPlus1 * (radiusPlus1 + 1) / 2;
        var effectStart = new this.Blureffect();
        var effect = effectStart;
        for (i = 1; i < div; i++) {
            effect = effect.next = new this.Blureffect();
            if (i == radiusPlus1) {
                var effectEnd = effect;
            }
        }
        effect.next = effectStart;
        var effectIn = null;
        var effectOut = null;
        yw = yi = 0;
        var mul_sum = this.mul_table[radius];
        var shg_sum = this.shg_table[radius];
        for (y = 0; y < height; y++) {
            r_in_sum = g_in_sum = b_in_sum = a_in_sum = r_sum = g_sum = b_sum = a_sum = 0;
            r_out_sum = radiusPlus1 * (pr = pixels[yi]);
            g_out_sum = radiusPlus1 * (pg = pixels[yi + 1]);
            b_out_sum = radiusPlus1 * (pb = pixels[yi + 2]);
            a_out_sum = radiusPlus1 * (pa = pixels[yi + 3]);
            r_sum += sumFactor * pr;
            g_sum += sumFactor * pg;
            b_sum += sumFactor * pb;
            a_sum += sumFactor * pa;
            effect = effectStart;
            for (i = 0; i < radiusPlus1; i++) {
                effect.r = pr;
                effect.g = pg;
                effect.b = pb;
                effect.a = pa;
                effect = effect.next;
            }
            for (i = 1; i < radiusPlus1; i++) {
                p = yi + ((widthMinus1 < i ? widthMinus1 : i) << 2);
                r_sum += (effect.r = (pr = pixels[p])) * (rbs = radiusPlus1 - i);
                g_sum += (effect.g = (pg = pixels[p + 1])) * rbs;
                b_sum += (effect.b = (pb = pixels[p + 2])) * rbs;
                a_sum += (effect.a = (pa = pixels[p + 3])) * rbs;
                r_in_sum += pr;
                g_in_sum += pg;
                b_in_sum += pb;
                a_in_sum += pa;
                effect = effect.next;
            }
            effectIn = effectStart;
            effectOut = effectEnd;
            for (x = 0; x < width; x++) {
                pixels[yi + 3] = pa = (a_sum * mul_sum) >> shg_sum;
                if (pa != 0) {
                    pa = 255 / pa;
                    pixels[yi] = ((r_sum * mul_sum) >> shg_sum) * pa;
                    pixels[yi + 1] = ((g_sum * mul_sum) >> shg_sum) * pa;
                    pixels[yi + 2] = ((b_sum * mul_sum) >> shg_sum) * pa;
                } else {
                    pixels[yi] = pixels[yi + 1] = pixels[yi + 2] = 0;
                }
                r_sum -= r_out_sum;
                g_sum -= g_out_sum;
                b_sum -= b_out_sum;
                a_sum -= a_out_sum;
                r_out_sum -= effectIn.r;
                g_out_sum -= effectIn.g;
                b_out_sum -= effectIn.b;
                a_out_sum -= effectIn.a;
                p = (yw + ((p = x + radius + 1) < widthMinus1 ? p : widthMinus1)) << 2;
                r_in_sum += (effectIn.r = pixels[p]);
                g_in_sum += (effectIn.g = pixels[p + 1]);
                b_in_sum += (effectIn.b = pixels[p + 2]);
                a_in_sum += (effectIn.a = pixels[p + 3]);
                r_sum += r_in_sum;
                g_sum += g_in_sum;
                b_sum += b_in_sum;
                a_sum += a_in_sum;
                effectIn = effectIn.next;
                r_out_sum += (pr = effectOut.r);
                g_out_sum += (pg = effectOut.g);
                b_out_sum += (pb = effectOut.b);
                a_out_sum += (pa = effectOut.a);
                r_in_sum -= pr;
                g_in_sum -= pg;
                b_in_sum -= pb;
                a_in_sum -= pa;
                effectOut = effectOut.next;
                yi += 4;
            }
            yw += width;
        }
        for (x = 0; x < width; x++) {
            g_in_sum = b_in_sum = a_in_sum = r_in_sum = g_sum = b_sum = a_sum = r_sum = 0;
            yi = x << 2;
            r_out_sum = radiusPlus1 * (pr = pixels[yi]);
            g_out_sum = radiusPlus1 * (pg = pixels[yi + 1]);
            b_out_sum = radiusPlus1 * (pb = pixels[yi + 2]);
            a_out_sum = radiusPlus1 * (pa = pixels[yi + 3]);
            r_sum += sumFactor * pr;
            g_sum += sumFactor * pg;
            b_sum += sumFactor * pb;
            a_sum += sumFactor * pa;
            effect = effectStart;
            for (i = 0; i < radiusPlus1; i++) {
                effect.r = pr;
                effect.g = pg;
                effect.b = pb;
                effect.a = pa;
                effect = effect.next;
            }
            yp = width;
            for (i = 1; i <= radius; i++) {
                yi = (yp + x) << 2;
                r_sum += (effect.r = (pr = pixels[yi])) * (rbs = radiusPlus1 - i);
                g_sum += (effect.g = (pg = pixels[yi + 1])) * rbs;
                b_sum += (effect.b = (pb = pixels[yi + 2])) * rbs;
                a_sum += (effect.a = (pa = pixels[yi + 3])) * rbs;
                r_in_sum += pr;
                g_in_sum += pg;
                b_in_sum += pb;
                a_in_sum += pa;
                effect = effect.next;
                if (i < heightMinus1) {
                    yp += width;
                }
            }
            yi = x;
            effectIn = effectStart;
            effectOut = effectEnd;
            for (y = 0; y < height; y++) {
                p = yi << 2;
                pixels[p + 3] = pa = (a_sum * mul_sum) >> shg_sum;
                if (pa > 0) {
                    pa = 255 / pa;
                    pixels[p] = ((r_sum * mul_sum) >> shg_sum) * pa;
                    pixels[p + 1] = ((g_sum * mul_sum) >> shg_sum) * pa;
                    pixels[p + 2] = ((b_sum * mul_sum) >> shg_sum) * pa;
                } else {
                    pixels[p] = pixels[p + 1] = pixels[p + 2] = 0;
                }
                r_sum -= r_out_sum;
                g_sum -= g_out_sum;
                b_sum -= b_out_sum;
                a_sum -= a_out_sum;
                r_out_sum -= effectIn.r;
                g_out_sum -= effectIn.g;
                b_out_sum -= effectIn.b;
                a_out_sum -= effectIn.a;
                p = (x + (((p = y + radiusPlus1) < heightMinus1 ? p : heightMinus1) * width)) << 2;
                r_sum += (r_in_sum += (effectIn.r = pixels[p]));
                g_sum += (g_in_sum += (effectIn.g = pixels[p + 1]));
                b_sum += (b_in_sum += (effectIn.b = pixels[p + 2]));
                a_sum += (a_in_sum += (effectIn.a = pixels[p + 3]));
                effectIn = effectIn.next;
                r_out_sum += (pr = effectOut.r);
                g_out_sum += (pg = effectOut.g);
                b_out_sum += (pb = effectOut.b);
                a_out_sum += (pa = effectOut.a);
                r_in_sum -= pr;
                g_in_sum -= pg;
                b_in_sum -= pb;
                a_in_sum -= pa;
                effectOut = effectOut.next;
                yi += width;
            }
        }
        imageData.data = pixels;
        this.context.putImageData(imageData, 0, 0);
    };

    this.effectBlurCanvasRGB = function (top_x, top_y, radius) {
        if (isNaN(radius) || radius < 1) {
            return;
        }
        radius |= 0;
        var width = this.canvas.width;
        var height = this.canvas.height;
        var imageData = this.context.getImageData(0, 0, width, height);
        var pixels = imageData.data;
        var x, y, i, p, yp, yi, yw, r_sum, g_sum, b_sum, r_out_sum, g_out_sum, b_out_sum, r_in_sum, g_in_sum, b_in_sum, pr, pg, pb, rbs;
        var div = radius + radius + 1;
        var w4 = width << 2;
        var widthMinus1 = width - 1;
        var heightMinus1 = height - 1;
        var radiusPlus1 = radius + 1;
        var sumFactor = radiusPlus1 * (radiusPlus1 + 1) / 2;
        var effectStart = new this.Blureffect();
        var effect = effectStart;
        for (i = 1; i < div; i++) {
            effect = effect.next = new this.Blureffect();
            if (i == radiusPlus1) {
                var effectEnd = effect;
            }
        }
        effect.next = effectStart;
        var effectIn = null;
        var effectOut = null;
        yw = yi = 0;
        var mul_sum = this.mul_table[radius];
        var shg_sum = this.shg_table[radius];
        for (y = 0; y < height; y++) {
            r_in_sum = g_in_sum = b_in_sum = r_sum = g_sum = b_sum = 0;
            r_out_sum = radiusPlus1 * (pr = pixels[yi]);
            g_out_sum = radiusPlus1 * (pg = pixels[yi + 1]);
            b_out_sum = radiusPlus1 * (pb = pixels[yi + 2]);
            r_sum += sumFactor * pr;
            g_sum += sumFactor * pg;
            b_sum += sumFactor * pb;
            effect = effectStart;
            for (i = 0; i < radiusPlus1; i++) {
                effect.r = pr;
                effect.g = pg;
                effect.b = pb;
                effect = effect.next;
            }
            for (i = 1; i < radiusPlus1; i++) {
                p = yi + ((widthMinus1 < i ? widthMinus1 : i) << 2);
                r_sum += (effect.r = (pr = pixels[p])) * (rbs = radiusPlus1 - i);
                g_sum += (effect.g = (pg = pixels[p + 1])) * rbs;
                b_sum += (effect.b = (pb = pixels[p + 2])) * rbs;
                r_in_sum += pr;
                g_in_sum += pg;
                b_in_sum += pb;
                effect = effect.next;
            }
            effectIn = effectStart;
            effectOut = effectEnd;
            for (x = 0; x < width; x++) {
                pixels[yi] = (r_sum * mul_sum) >> shg_sum;
                pixels[yi + 1] = (g_sum * mul_sum) >> shg_sum;
                pixels[yi + 2] = (b_sum * mul_sum) >> shg_sum;
                r_sum -= r_out_sum;
                g_sum -= g_out_sum;
                b_sum -= b_out_sum;
                r_out_sum -= effectIn.r;
                g_out_sum -= effectIn.g;
                b_out_sum -= effectIn.b;
                p = (yw + ((p = x + radius + 1) < widthMinus1 ? p : widthMinus1)) << 2;
                r_in_sum += (effectIn.r = pixels[p]);
                g_in_sum += (effectIn.g = pixels[p + 1]);
                b_in_sum += (effectIn.b = pixels[p + 2]);
                r_sum += r_in_sum;
                g_sum += g_in_sum;
                b_sum += b_in_sum;
                effectIn = effectIn.next;
                r_out_sum += (pr = effectOut.r);
                g_out_sum += (pg = effectOut.g);
                b_out_sum += (pb = effectOut.b);
                r_in_sum -= pr;
                g_in_sum -= pg;
                b_in_sum -= pb;
                effectOut = effectOut.next;
                yi += 4;
            }
            yw += width;
        }
        for (x = 0; x < width; x++) {
            g_in_sum = b_in_sum = r_in_sum = g_sum = b_sum = r_sum = 0;
            yi = x << 2;
            r_out_sum = radiusPlus1 * (pr = pixels[yi]);
            g_out_sum = radiusPlus1 * (pg = pixels[yi + 1]);
            b_out_sum = radiusPlus1 * (pb = pixels[yi + 2]);
            r_sum += sumFactor * pr;
            g_sum += sumFactor * pg;
            b_sum += sumFactor * pb;
            effect = effectStart;
            for (i = 0; i < radiusPlus1; i++) {
                effect.r = pr;
                effect.g = pg;
                effect.b = pb;
                effect = effect.next;
            }
            yp = width;
            for (i = 1; i <= radius; i++) {
                yi = (yp + x) << 2;
                r_sum += (effect.r = (pr = pixels[yi])) * (rbs = radiusPlus1 - i);
                g_sum += (effect.g = (pg = pixels[yi + 1])) * rbs;
                b_sum += (effect.b = (pb = pixels[yi + 2])) * rbs;
                r_in_sum += pr;
                g_in_sum += pg;
                b_in_sum += pb;
                effect = effect.next;
                if (i < heightMinus1) {
                    yp += width;
                }
            }
            yi = x;
            effectIn = effectStart;
            effectOut = effectEnd;
            for (y = 0; y < height; y++) {
                p = yi << 2;
                pixels[p] = (r_sum * mul_sum) >> shg_sum;
                pixels[p + 1] = (g_sum * mul_sum) >> shg_sum;
                pixels[p + 2] = (b_sum * mul_sum) >> shg_sum;
                r_sum -= r_out_sum;
                g_sum -= g_out_sum;
                b_sum -= b_out_sum;
                r_out_sum -= effectIn.r;
                g_out_sum -= effectIn.g;
                b_out_sum -= effectIn.b;
                p = (x + (((p = y + radiusPlus1) < heightMinus1 ? p : heightMinus1) * width)) << 2;
                r_sum += (r_in_sum += (effectIn.r = pixels[p]));
                g_sum += (g_in_sum += (effectIn.g = pixels[p + 1]));
                b_sum += (b_in_sum += (effectIn.b = pixels[p + 2]));
                effectIn = effectIn.next;
                r_out_sum += (pr = effectOut.r);
                g_out_sum += (pg = effectOut.g);
                b_out_sum += (pb = effectOut.b);
                r_in_sum -= pr;
                g_in_sum -= pg;
                b_in_sum -= pb;
                effectOut = effectOut.next;
                yi += width;
            }
        }
        imageData.data = pixels;
        this.context.putImageData(imageData, 0, 0);
    };

    this.compoundBlurImage = function (radiusData, minRadius, increaseFactor, blurLevels, blurAlphaChannel) {
        var w = this.canvas.width;
        var h = this.canvas.height;
        this.restoreUndo();
        if (isNaN(minRadius) || minRadius <= 0 || isNaN(increaseFactor) || increaseFactor == 0) {
            return;
        }
        if (blurAlphaChannel) {
            this.compundBlurCanvasRGBA(0, 0, w, h, radiusData, minRadius, increaseFactor, blurLevels);
        } else {
            this.compundBlurCanvasRGB(0, 0, w, h, radiusData, minRadius, increaseFactor, blurLevels);
        }
    };

    this.getLinearGradientMap = function (width, height, centerX, centerY, angle, length, mirrored) {
        var cnv = document.createElement('canvas');
        cnv.width = width;
        cnv.height = height;
        var x1 = centerX + Math.cos(angle) * length * 0.5;
        var y1 = centerY + Math.sin(angle) * length * 0.5;
        var x2 = centerX - Math.cos(angle) * length * 0.5;
        var y2 = centerY - Math.sin(angle) * length * 0.5;
        var context = cnv.getContext("2d");
        var gradient = context.createLinearGradient(x1, y1, x2, y2);
        if (!mirrored) {
            gradient.addColorStop(0, "white");
            gradient.addColorStop(1, "black");
        } else {
            gradient.addColorStop(0, "white");
            gradient.addColorStop(0.5, "black");
            gradient.addColorStop(1, "white");
        }
        context.fillStyle = gradient;
        context.fillRect(0, 0, width, height);
        return context.getImageData(0, 0, width, height);
    };

    this.effectBlurImage = function (radius, blurAlphaChannel) {
        this.restoreUndo();
        if (isNaN(radius) || radius < 1) {
            return;
        }
        if (blurAlphaChannel) {
            this.effectBlurCanvasRGBA(0, 0, radius);
        } else {
            this.effectBlurCanvasRGB(0, 0, radius);
        }
    };

    this.Blureffect = function () {
        this.r = 0;
        this.g = 0;
        this.b = 0;
        this.a = 0;
        this.next = null;
    };

    this.getRadialGradientMap = function (width, height, centerX, centerY, radius1, radius2) {
        var cnv = document.createElement('canvas');
        cnv.width = width;
        cnv.height = height;
        var context = cnv.getContext("2d");
        var gradient = context.createRadialGradient(centerX, centerY, radius1, centerX, centerY, radius2);
        gradient.addColorStop(1, "white");
        gradient.addColorStop(0, "black");
        context.fillStyle = gradient;
        context.fillRect(0, 0, width, height);
        return context.getImageData(0, 0, width, height);
    };

    this.compundBlurCanvasRGB = function (top_x, top_y, width, height, radiusData, minRadius, increaseFactor, blurLevels) {
        if (isNaN(minRadius) || minRadius <= 0 || isNaN(increaseFactor) || increaseFactor == 0) {
            return;
        }
        var imageData = this.context.getImageData(0, 0, this.canvas.width, this.canvas.height);
        this.renderCompundBlurRGB(imageData, radiusData, width, height, minRadius, increaseFactor, blurLevels);
        this.context.putImageData(imageData, top_x, top_y);
    };

    this.compundBlurCanvasRGBA = function (top_x, top_y, width, height, radiusData, minRadius, increaseFactor, blurLevels) {
        if (isNaN(minRadius) || minRadius <= 0 || isNaN(increaseFactor) || increaseFactor == 0) {
            return;
        }
        var imageData = this.context.getImageData(0, 0, this.canvas.width, this.canvas.height);
        this.renderCompundBlurRGBA(imageData, radiusData, width, height, minRadius, increaseFactor, blurLevels);
        this.context.putImageData(imageData, top_x, top_y);
    };

    this.renderCompundBlurRGB = function (imageData, radiusData, width, height, radius, increaseFactor, blurLevels) {
        var x, y, i, p, yp, yi, yw, r_sum, g_sum, b_sum, r_out_sum, g_out_sum, b_out_sum, r_in_sum, g_in_sum, b_in_sum, pr, pg, pb, rbs;
        var imagePixels = imageData.data;
        var radiusPixels = radiusData.data;
        var wh = width * height;
        var wh4 = wh << 2;
        var pixels = [];
        for (var i = 0; i < wh4; i++) {
            pixels[i] = imagePixels[i];
        }
        var currentIndex = 0;
        var steps = blurLevels;
        blurLevels -= 1;
        while (steps-- >= 0) {
            var iradius = (radius + 0.5) | 0;
            if (iradius == 0) {
                continue;
            }
            if (iradius > 256) {
                iradius = 256;
            }
            var div = iradius + iradius + 1;
            var w4 = width << 2;
            var widthMinus1 = width - 1;
            var heightMinus1 = height - 1;
            var radiusPlus1 = iradius + 1;
            var sumFactor = radiusPlus1 * (radiusPlus1 + 1) / 2;
            var effectStart = new this.Blureffect();
            var effect = effectStart;
            for (i = 1; i < div; i++) {
                effect = effect.next = new this.Blureffect();
                if (i == radiusPlus1) {
                    var effectEnd = effect;
                }
            }
            effect.next = effectStart;
            var effectIn = null;
            var effectOut = null;
            yw = yi = 0;
            var mul_sum = this.mul_table[iradius];
            var shg_sum = this.shg_table[iradius];
            for (y = 0; y < height; y++) {
                r_in_sum = g_in_sum = b_in_sum = r_sum = g_sum = b_sum = 0;
                r_out_sum = radiusPlus1 * (pr = pixels[yi]);
                g_out_sum = radiusPlus1 * (pg = pixels[yi + 1]);
                b_out_sum = radiusPlus1 * (pb = pixels[yi + 2]);
                r_sum += sumFactor * pr;
                g_sum += sumFactor * pg;
                b_sum += sumFactor * pb;
                effect = effectStart;
                for (i = 0; i < radiusPlus1; i++) {
                    effect.r = pr;
                    effect.g = pg;
                    effect.b = pb;
                    effect = effect.next;
                }
                for (i = 1; i < radiusPlus1; i++) {
                    p = yi + ((widthMinus1 < i ? widthMinus1 : i) << 2);
                    r_sum += (effect.r = (pr = pixels[p])) * (rbs = radiusPlus1 - i);
                    g_sum += (effect.g = (pg = pixels[p + 1])) * rbs;
                    b_sum += (effect.b = (pb = pixels[p + 2])) * rbs;
                    r_in_sum += pr;
                    g_in_sum += pg;
                    b_in_sum += pb;
                    effect = effect.next;
                }
                effectIn = effectStart;
                effectOut = effectEnd;
                for (x = 0; x < width; x++) {
                    pixels[yi] = (r_sum * mul_sum) >> shg_sum;
                    pixels[yi + 1] = (g_sum * mul_sum) >> shg_sum;
                    pixels[yi + 2] = (b_sum * mul_sum) >> shg_sum;
                    r_sum -= r_out_sum;
                    g_sum -= g_out_sum;
                    b_sum -= b_out_sum;
                    r_out_sum -= effectIn.r;
                    g_out_sum -= effectIn.g;
                    b_out_sum -= effectIn.b;
                    p = (yw + ((p = x + radiusPlus1) < widthMinus1 ? p : widthMinus1)) << 2;
                    r_in_sum += (effectIn.r = pixels[p]);
                    g_in_sum += (effectIn.g = pixels[p + 1]);
                    b_in_sum += (effectIn.b = pixels[p + 2]);
                    r_sum += r_in_sum;
                    g_sum += g_in_sum;
                    b_sum += b_in_sum;
                    effectIn = effectIn.next;
                    r_out_sum += (pr = effectOut.r);
                    g_out_sum += (pg = effectOut.g);
                    b_out_sum += (pb = effectOut.b);
                    r_in_sum -= pr;
                    g_in_sum -= pg;
                    b_in_sum -= pb;
                    effectOut = effectOut.next;
                    yi += 4;
                }
                yw += width;
            }
            for (x = 0; x < width; x++) {
                g_in_sum = b_in_sum = r_in_sum = g_sum = b_sum = r_sum = 0;
                yi = x << 2;
                r_out_sum = radiusPlus1 * (pr = pixels[yi]);
                g_out_sum = radiusPlus1 * (pg = pixels[yi + 1]);
                b_out_sum = radiusPlus1 * (pb = pixels[yi + 2]);
                r_sum += sumFactor * pr;
                g_sum += sumFactor * pg;
                b_sum += sumFactor * pb;
                effect = effectStart;
                for (i = 0; i < radiusPlus1; i++) {
                    effect.r = pr;
                    effect.g = pg;
                    effect.b = pb;
                    effect = effect.next;
                }
                yp = width;
                for (i = 1; i < radiusPlus1; i++) {
                    yi = (yp + x) << 2;
                    r_sum += (effect.r = (pr = pixels[yi])) * (rbs = radiusPlus1 - i);
                    g_sum += (effect.g = (pg = pixels[yi + 1])) * rbs;
                    b_sum += (effect.b = (pb = pixels[yi + 2])) * rbs;
                    r_in_sum += pr;
                    g_in_sum += pg;
                    b_in_sum += pb;
                    effect = effect.next;
                    if (i < heightMinus1) {
                        yp += width;
                    }
                }
                yi = x;
                effectIn = effectStart;
                effectOut = effectEnd;
                for (y = 0; y < height; y++) {
                    p = yi << 2;
                    pixels[p] = (r_sum * mul_sum) >> shg_sum;
                    pixels[p + 1] = (g_sum * mul_sum) >> shg_sum;
                    pixels[p + 2] = (b_sum * mul_sum) >> shg_sum;
                    r_sum -= r_out_sum;
                    g_sum -= g_out_sum;
                    b_sum -= b_out_sum;
                    r_out_sum -= effectIn.r;
                    g_out_sum -= effectIn.g;
                    b_out_sum -= effectIn.b;
                    p = (x + (((p = y + radiusPlus1) < heightMinus1 ? p : heightMinus1) * width)) << 2;
                    r_sum += (r_in_sum += (effectIn.r = pixels[p]));
                    g_sum += (g_in_sum += (effectIn.g = pixels[p + 1]));
                    b_sum += (b_in_sum += (effectIn.b = pixels[p + 2]));
                    effectIn = effectIn.next;
                    r_out_sum += (pr = effectOut.r);
                    g_out_sum += (pg = effectOut.g);
                    b_out_sum += (pb = effectOut.b);
                    r_in_sum -= pr;
                    g_in_sum -= pg;
                    b_in_sum -= pb;
                    effectOut = effectOut.next;
                    yi += width;
                }
            }
            radius *= increaseFactor;
            for (i = wh; --i > -1; ) {
                var idx = i << 2;
                var lookupValue = (radiusPixels[idx + 2] & 0xff) / 255.0 * blurLevels;
                var index = lookupValue | 0;
                if (index == currentIndex) {
                    var blend = 256.0 * (lookupValue - (lookupValue | 0));
                    var iblend = 256 - blend;
                    imagePixels[idx] = (imagePixels[idx] * iblend + pixels[idx] * blend) >> 8;
                    imagePixels[idx + 1] = (imagePixels[idx + 1] * iblend + pixels[idx + 1] * blend) >> 8;
                    imagePixels[idx + 2] = (imagePixels[idx + 2] * iblend + pixels[idx + 2] * blend) >> 8;
                } else if (index == currentIndex + 1) {
                    imagePixels[idx] = pixels[idx];
                    imagePixels[idx + 1] = pixels[idx + 1];
                    imagePixels[idx + 2] = pixels[idx + 2];
                }
            }
            currentIndex++;
        }
    };

    this.renderCompundBlurRGBA = function (imageData, radiusData, width, height, radius, increaseFactor, blurLevels) {
        var x, y, i, p, yp, yi, yw, r_sum, g_sum, b_sum, a_sum, r_out_sum, g_out_sum, b_out_sum, a_out_sum, r_in_sum, g_in_sum, b_in_sum, a_in_sum, pa, pr, pg, pb, rbs;
        var imagePixels = imageData.data;
        var radiusPixels = radiusData.data;
        var wh = width * height;
        var wh4 = wh << 2;
        var pixels = [];
        for (var i = 0; i < wh4; i++) {
            pixels[i] = imagePixels[i];
        }
        var currentIndex = 0;
        var steps = blurLevels;
        blurLevels -= 1;
        while (steps-- >= 0) {
            var iradius = (radius + 0.5) | 0;
            if (iradius == 0) {
                continue;
            }
            if (iradius > 256) {
                iradius = 256;
            }
            var div = iradius + iradius + 1;
            var w4 = width << 2;
            var widthMinus1 = width - 1;
            var heightMinus1 = height - 1;
            var radiusPlus1 = iradius + 1;
            var sumFactor = radiusPlus1 * (radiusPlus1 + 1) / 2;
            var effectStart = new this.Blureffect();
            var effect = effectStart;
            for (i = 1; i < div; i++) {
                effect = effect.next = new this.Blureffect();
                if (i == radiusPlus1) {
                    var effectEnd = effect;
                }
            }
            effect.next = effectStart;
            var effectIn = null;
            var effectOut = null;
            yw = yi = 0;
            var mul_sum = this.mul_table[iradius];
            var shg_sum = this.shg_table[iradius];
            for (y = 0; y < height; y++) {
                r_in_sum = g_in_sum = b_in_sum = a_in_sum = r_sum = g_sum = b_sum = a_sum = 0;
                r_out_sum = radiusPlus1 * (pr = pixels[yi]);
                g_out_sum = radiusPlus1 * (pg = pixels[yi + 1]);
                b_out_sum = radiusPlus1 * (pb = pixels[yi + 2]);
                a_out_sum = radiusPlus1 * (pa = pixels[yi + 3]);
                r_sum += sumFactor * pr;
                g_sum += sumFactor * pg;
                b_sum += sumFactor * pb;
                a_sum += sumFactor * pa;
                effect = effectStart;
                for (i = 0; i < radiusPlus1; i++) {
                    effect.r = pr;
                    effect.g = pg;
                    effect.b = pb;
                    effect.a = pa;
                    effect = effect.next;
                }
                for (i = 1; i < radiusPlus1; i++) {
                    p = yi + ((widthMinus1 < i ? widthMinus1 : i) << 2);
                    r_sum += (effect.r = (pr = pixels[p])) * (rbs = radiusPlus1 - i);
                    g_sum += (effect.g = (pg = pixels[p + 1])) * rbs;
                    b_sum += (effect.b = (pb = pixels[p + 2])) * rbs;
                    a_sum += (effect.a = (pa = pixels[p + 3])) * rbs;
                    r_in_sum += pr;
                    g_in_sum += pg;
                    b_in_sum += pb;
                    a_in_sum += pa;
                    effect = effect.next;
                }
                effectIn = effectStart;
                effectOut = effectEnd;
                for (x = 0; x < width; x++) {
                    pixels[yi + 3] = pa = (a_sum * mul_sum) >> shg_sum;
                    if (pa != 0) {
                        pa = 255 / pa;
                        pixels[yi] = ((r_sum * mul_sum) >> shg_sum) * pa;
                        pixels[yi + 1] = ((g_sum * mul_sum) >> shg_sum) * pa;
                        pixels[yi + 2] = ((b_sum * mul_sum) >> shg_sum) * pa;
                    } else {
                        pixels[yi] = pixels[yi + 1] = pixels[yi + 2] = 0;
                    }
                    r_sum -= r_out_sum;
                    g_sum -= g_out_sum;
                    b_sum -= b_out_sum;
                    a_sum -= a_out_sum;
                    r_out_sum -= effectIn.r;
                    g_out_sum -= effectIn.g;
                    b_out_sum -= effectIn.b;
                    a_out_sum -= effectIn.a;
                    p = (yw + ((p = x + radiusPlus1) < widthMinus1 ? p : widthMinus1)) << 2;
                    r_in_sum += (effectIn.r = pixels[p]);
                    g_in_sum += (effectIn.g = pixels[p + 1]);
                    b_in_sum += (effectIn.b = pixels[p + 2]);
                    a_in_sum += (effectIn.a = pixels[p + 3]);
                    r_sum += r_in_sum;
                    g_sum += g_in_sum;
                    b_sum += b_in_sum;
                    a_sum += a_in_sum;
                    effectIn = effectIn.next;
                    r_out_sum += (pr = effectOut.r);
                    g_out_sum += (pg = effectOut.g);
                    b_out_sum += (pb = effectOut.b);
                    a_out_sum += (pa = effectOut.a);
                    r_in_sum -= pr;
                    g_in_sum -= pg;
                    b_in_sum -= pb;
                    a_in_sum -= pa;
                    effectOut = effectOut.next;
                    yi += 4;
                }
                yw += width;
            }
            for (x = 0; x < width; x++) {
                g_in_sum = b_in_sum = a_in_sum = r_in_sum = g_sum = b_sum = a_sum = r_sum = 0;
                yi = x << 2;
                r_out_sum = radiusPlus1 * (pr = pixels[yi]);
                g_out_sum = radiusPlus1 * (pg = pixels[yi + 1]);
                b_out_sum = radiusPlus1 * (pb = pixels[yi + 2]);
                a_out_sum = radiusPlus1 * (pa = pixels[yi + 3]);
                r_sum += sumFactor * pr;
                g_sum += sumFactor * pg;
                b_sum += sumFactor * pb;
                a_sum += sumFactor * pa;
                effect = effectStart;
                for (i = 0; i < radiusPlus1; i++) {
                    effect.r = pr;
                    effect.g = pg;
                    effect.b = pb;
                    effect.a = pa;
                    effect = effect.next;
                }
                yp = width;
                for (i = 1; i < radiusPlus1; i++) {
                    yi = (yp + x) << 2;
                    r_sum += (effect.r = (pr = pixels[yi])) * (rbs = radiusPlus1 - i);
                    g_sum += (effect.g = (pg = pixels[yi + 1])) * rbs;
                    b_sum += (effect.b = (pb = pixels[yi + 2])) * rbs;
                    a_sum += (effect.a = (pa = pixels[yi + 3])) * rbs;
                    r_in_sum += pr;
                    g_in_sum += pg;
                    b_in_sum += pb;
                    a_in_sum += pa;
                    effect = effect.next;
                    if (i < heightMinus1) {
                        yp += width;
                    }
                }
                yi = x;
                effectIn = effectStart;
                effectOut = effectEnd;
                for (y = 0; y < height; y++) {
                    p = yi << 2;
                    pixels[p + 3] = pa = (a_sum * mul_sum) >> shg_sum;
                    if (pa > 0) {
                        pa = 255 / pa;
                        pixels[p] = ((r_sum * mul_sum) >> shg_sum) * pa;
                        pixels[p + 1] = ((g_sum * mul_sum) >> shg_sum) * pa;
                        pixels[p + 2] = ((b_sum * mul_sum) >> shg_sum) * pa;
                    } else {
                        pixels[p] = pixels[p + 1] = pixels[p + 2] = 0;
                    }
                    r_sum -= r_out_sum;
                    g_sum -= g_out_sum;
                    b_sum -= b_out_sum;
                    a_sum -= a_out_sum;
                    r_out_sum -= effectIn.r;
                    g_out_sum -= effectIn.g;
                    b_out_sum -= effectIn.b;
                    a_out_sum -= effectIn.a;
                    p = (x + (((p = y + radiusPlus1) < heightMinus1 ? p : heightMinus1) * width)) << 2;
                    r_sum += (r_in_sum += (effectIn.r = pixels[p]));
                    g_sum += (g_in_sum += (effectIn.g = pixels[p + 1]));
                    b_sum += (b_in_sum += (effectIn.b = pixels[p + 2]));
                    a_sum += (a_in_sum += (effectIn.a = pixels[p + 3]));
                    effectIn = effectIn.next;
                    r_out_sum += (pr = effectOut.r);
                    g_out_sum += (pg = effectOut.g);
                    b_out_sum += (pb = effectOut.b);
                    a_out_sum += (pa = effectOut.a);
                    r_in_sum -= pr;
                    g_in_sum -= pg;
                    b_in_sum -= pb;
                    a_in_sum -= pa;
                    effectOut = effectOut.next;
                    yi += width;
                }
            }
            radius *= increaseFactor;
            for (i = wh; --i > -1; ) {
                var idx = i << 2;
                var lookupValue = (radiusPixels[idx + 2] & 0xff) / 255.0 * blurLevels;
                var index = lookupValue | 0;
                if (index == currentIndex) {
                    var blend = 256.0 * (lookupValue - (lookupValue | 0));
                    var iblend = 256 - blend;
                    imagePixels[idx] = (imagePixels[idx] * iblend + pixels[idx] * blend) >> 8;
                    imagePixels[idx + 1] = (imagePixels[idx + 1] * iblend + pixels[idx + 1] * blend) >> 8;
                    imagePixels[idx + 2] = (imagePixels[idx + 2] * iblend + pixels[idx + 2] * blend) >> 8;
                    imagePixels[idx + 3] = (imagePixels[idx + 3] * iblend + pixels[idx + 3] * blend) >> 8;
                } else if (index == currentIndex + 1) {
                    imagePixels[idx] = pixels[idx];
                    imagePixels[idx + 1] = pixels[idx + 1];
                    imagePixels[idx + 2] = pixels[idx + 2];
                    imagePixels[idx + 3] = pixels[idx + 3];
                }
            }
            currentIndex++;
        }
    };

    this.getCamera = function () {
        var self = this;
        navigator.getMedia = (navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia);
        navigator.getMedia({
            video: true,
            audio: false
        }, function (stream) {
            if (navigator.mozGetUserMedia) {
                self.video.mozSrcObject = stream;
            } else {
                var vendorURL = window.URL || window.webkitURL;
                self.video.src = vendorURL.createObjectURL(stream);
            }
            self.video.play();
            self.streaming = stream;
        }, function (err) {
            if (window.console) {
                console.log('An error occured! ' + err);
            }
        });
        this.brightnessValue = 0;
        this.contrastValue = 0;
        this.saturationValue = 1;
        this.noiseValue = 0;
        this.vignette = false;
        this.effect = 'none';
        this.currentFilter = 'Original';
    };

    this.applyRotation = function () {
        this.degrees += 90;
        if (this.degrees >= 360) {
            this.degrees = 0;
        }
        if (this.degrees === 0 || this.degrees === 180) {
            this.canvas.width = this.tempCanvas.width;
            this.canvas.height = this.tempCanvas.height;
        } else {
            this.canvas.width = this.tempCanvas.height;
            this.canvas.height = this.tempCanvas.width;
        }
        this.context.save();
        this.context.translate(this.canvas.width / 2, this.canvas.height / 2);
        this.context.rotate(this.degrees * Math.PI / 180);
        this.context.drawImage(this.tempCanvas, -this.tempCanvas.width * 0.5, -this.tempCanvas.height * 0.5);
        this.context.restore();
        this.canvasPosition();
    };

    this.importImage = function () {
        var self = this;
        clearInterval(self.streamInterval);
        this.streaming.getTracks()[0].stop();
        this.streaming.stop();
        this.isStreaming = false;
        this.image.src = this.canvas.toDataURL();
        this.image.onload = function () {
            self.initCanvas();
        };
    };

    this.canvasPosition = function () {
        $('#pico-x-canvas').css('max-height', $(window).height() - 192);
        $('#pico-x-canvas').css('margin-top', -$("#pico-x-canvas").height() / 2 - 4);
        $('#pico-x-canvas').css('margin-left', -$("#pico-x-canvas").width() / 2);
    };

    this.showFilters = function () {
        this.saveUndo();
        $('.setting').each(function () {
            $(this).hide();
        });
        $('.theme').each(function () {
            $(this).hide();
        });
        $('#pico-x-filters, .pico-x-show-undo').show();
        $('.setting').perfectScrollbar('update');
        $('#title').text('Filters');
        $('#pico-x-canvas').swipe('enable');
        $('#pico-x-show-savecheckpoint').hide();
    };

    this.showPackage = function () {
        var test = $("#package-icon").hasClass("active");
        if (test === false) {
            this.saveUndo();
        }
        $("#pico-x-package").toggle();
        $('.theme').perfectScrollbar('update');
        $("#title").text("Package");
        $(".list-item").on("mouseenter", function () {
            $(this).addClass("active");
        });
        $(".list-item").on("mouseleave", function () {
            $(this).removeClass("active");
        });
    };

    this.showSettings = function () {
        this.saveUndo();
        $(".setting").each(function () {
            $(this).hide();
        });
        $(".theme").each(function () {
            $(this).hide();
        });
        $("#settings, .pico-x-show-undo").show();
        $("#title").text("Adjustments");
        $('.setting').perfectScrollbar('update');
        $("#pico-x-canvas").swipe("disable");
        this.initTempCanvas();
        $("#back, #pico-x-show-savecheckpoint").hide();
    };

    this.showCSettings = function () {
        this.saveUndo();
        $(".setting").each(function () {
            $(this).hide();
        });
        $(".theme").each(function () {
            $(this).hide();
        });
        $("#c-settings").show();
        $("#title").text("Camera");
        $("#c-back").hide();
        $('.setting').perfectScrollbar('update');
        $("#pico-x-canvas").swipe("disable");
    };

    this.showSaturation = function () {
        $(".setting").each(function () {
            $(this).hide();
        });
        $("#saturation").show();
        $('.setting').perfectScrollbar('update');
        $("#title").text("Saturation");
    };

    this.showEffects = function () {
        $(".setting").each(function () {
            $(this).hide();
        });
        $("#pico-x-effects, #back").show();
        $('.setting').perfectScrollbar('update');
        $("#title").text("Effects");
    };

    this.showBrightness = function () {
        $(".setting").each(function () {
            $(this).hide();
        });
        $("#brightness").show();
        $('.setting').perfectScrollbar('update');
        $("#title").text("Brightness");
    };

    this.showContrast = function () {
        $(".setting").each(function () {
            $(this).hide();
        });
        $("#contrast").show();
        $('.setting').perfectScrollbar('update');
        $("#title").text("Contrast");
    };

    this.showVignette = function () {
        $(".setting").each(function () {
            $(this).hide();
        });
        $("#vignette").show();
        $("#title").text("Vignette");
    };

    this.showBlur = function () {
        $(".setting").each(function () {
            $(this).hide();
        });
        $("#blur").show();
        $("#title").text("Blur");
    };

    this.showFlip = function () {
        $(".setting").each(function () {
            $(this).hide();
        });
        $("#flip").show();
        $("#title").text("Flip");
    };

    this.showRotation = function () {
        $(".setting").each(function () {
            $(this).hide();
        });
        $("#rotation").show();
        $("#title").text("Rotation");
        $('.pico-x-show-undo').hide();
    };

    this.showCrop = function () {
        $(".setting").each(function () {
            $(this).hide();
        });
        $("#crop, #back").show();
        $('.setting').perfectScrollbar('update');
        $('.pico-x-show-undo').hide();
        $("#title").text("Crop");
    };

    this.showSharpen = function () {
        $(".setting").each(function () {
            $(this).hide();
        });
        $("#sharpen").show();
        $("#title").text("Sharpen");
    };

    this.showFrames = function () {
        $(".setting").each(function () {
            $(this).hide();
        });
        $("#pico-x-frames, #back").show();
        $('.setting').perfectScrollbar('update');
        $("#title").text("Frames");
    };

    this.showBackgrounds = function () {
        $(".setting").each(function () {
            $(this).hide();
        });
        $("#pico-x-backgrounds, #back").show();
        $('.setting').perfectScrollbar('update');
        $("#title").text("Backgrounds");
    };

    this.showFrameblur = function () {
        $(".setting").each(function () {
            $(this).hide();
        });
        $("#frame-blur").show();
        $("#title").text("Frame Blur");
    };

    this.showRgb = function () {
        $(".setting").each(function () {
            $(this).hide();
        });
        $("#pico-x-rgb").show();
        $(".tab-footer a").css({
            "width": "20%",
            "display": "table-cell",
            "text-align": "center"
        });
        $("#pico-x-show-savecheckpoint").show();
        $("#title").text("RGB");
    };

    this.showNoise = function () {
        $(".setting").each(function () {
            $(this).hide();
        });
        $("#noise").show();
        $("#title").text("Noise");
    };

    this.showEditor = function () {
        $(".back, .pico-x-camera-btn, .pico-x-export, .interaction, .pico-x-show-undo, .pico-x-refresh-btn, .pico-x-gallery-btn, #pico-x-show-savecheckpoint").show();
        $(".pico-x-camera-take, #c-back, #back").hide();
        $("#pico-x-filters").removeClass("toright");
        $("#filters-icon").trigger("click");
    };

    this.initCanvas = function () {
        this.context = this.canvas.getContext('2d');
        this.canvas.width = this.image.width;
        this.canvas.height = this.image.height;
        this.context.drawImage(this.image, 0, 0, this.image.width, this.image.height, 0, 0, this.canvas.width, this.canvas.height);
        this.canvasPosition();
        this.saveUndo();
        this.loadFilterPreviews();
    };

    this.initTempCanvas = function () {
        this.tempCanvas = document.createElement('canvas');
        this.tempCanvas.width = this.canvas.width;
        this.tempCanvas.height = this.canvas.height;
        this.tempContext = this.tempCanvas.getContext('2d');
        this.tempContext.drawImage(this.canvas, 0, 0);
    };

    this.getUrlParams = function () {
        var results = new RegExp('[\?&]' + 'src' + '=([^&#]*)').exec(window.location.href);
        if (results === null) {
            return null;
        } else {
            return results[1] || 0;
        }
    };

    this.saveUndo = function () {
        this.checkPoint = this.context.getImageData(0, 0, this.canvas.width, this.canvas.height);
    };

    this.restoreUndo = function () {
        this.context.putImageData(this.checkPoint, 0, 0);
    };

    this.restore = function () {
        this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
        this.initCanvas();
        this.initTempCanvas();
        this.currentFilter = "Original";
        this.degrees = 0;
        $(".range").each(function () {
            $(this).val(0);
        });
        $('#s-range').val(1);
        this.saveUndo();
        this.canvasPosition();
    };

    this.applyPreviewFilter = function (filter) {
        var _canvas = document.createElement('canvas');
        _canvas.width = 50;
        _canvas.height = 50;
        var _ctx = _canvas.getContext("2d");
        _ctx.drawImage(this.canvas, 0, 0, this.canvas.width, this.canvas.height, 0, 0, 50, 50);
        var _filter = this.selection[filter];
        var canvas = $('.canvas-filter-' + filter)[0];
        var ctx = canvas.getContext("2d");
        var imageData = _ctx.getImageData(0, 0, _canvas.width, _canvas.height);
        var rgba = imageData.data;
        var length = rgba.length;
        for (var i = 0; i < length; i += 4) {
            rgba[i] = this.FILTERS[_filter].r[rgba[i]];
            rgba[i + 1] = this.FILTERS[_filter].g[rgba[i + 1]];
            rgba[i + 2] = this.FILTERS[_filter].b[rgba[i + 2]];
        }
        imageData.data = rgba;
        ctx.putImageData(imageData, 0, 0);
    };

    this.applyFilter = function (filter) {
        if (!this.isStreaming) {
            this.restoreUndo();
        }
        this.currentFilter = filter;
        var imageData = this.context.getImageData(0, 0, this.canvas.width, this.canvas.height);
        var rgba = imageData.data;
        var length = rgba.length;
        for (var i = 0; i < length; i += 4) {
            rgba[i] = this.FILTERS[filter].r[rgba[i]];
            rgba[i + 1] = this.FILTERS[filter].g[rgba[i + 1]];
            rgba[i + 2] = this.FILTERS[filter].b[rgba[i + 2]];
        }
        imageData.data = rgba;
        this.context.putImageData(imageData, 0, 0);
        this.showFilters();
    };

    this.applyFlipHorizontal = function () {
        this.context.save();
        this.context.translate(this.tempCanvas.width, 0);
        this.context.scale(-1, 1);
        this.context.drawImage(this.tempCanvas, 0, 0, this.tempCanvas.width, this.tempCanvas.height, 0, 0, this.canvas.width, this.canvas.height);
        this.context.restore();
    };

    this.applyBrightness = function (adjustment) {
        adjustment = parseInt(adjustment, 10);
        this.brightnessValue = adjustment;
        if (!this.isStreaming) {
            this.restoreUndo();
        }
        var imageData = this.context.getImageData(0, 0, this.canvas.width, this.canvas.height);
        var rgba = imageData.data;
        var length = rgba.length;
        for (var i = 0; i < length; i += 4) {
            rgba[i] += adjustment;
            rgba[i + 1] += adjustment;
            rgba[i + 2] += adjustment;
        }
        imageData.data = rgba;
        this.context.putImageData(imageData, 0, 0);
    };

    this.applyContrast = function (contrast) {
        contrast = parseInt(contrast, 10);
        this.contrastValue = contrast;
        if (!this.isStreaming) {
            this.restoreUndo();
        }
        var imageData = this.context.getImageData(0, 0, this.canvas.width, this.canvas.height);
        var rgba = imageData.data;
        var length = rgba.length;
        var factor = (259 * (contrast + 255)) / (255 * (259 - contrast));
        for (var i = 0; i < length; i += 4) {
            rgba[i] = factor * (rgba[i] - 128) + 128;
            rgba[i + 1] = factor * (rgba[i + 1] - 128) + 128;
            rgba[i + 2] = factor * (rgba[i + 2] - 128) + 128;
        }
        imageData.data = rgba;
        this.context.putImageData(imageData, 0, 0);
    };

    this.applySaturation = function (saturation) {
        saturation = parseFloat(saturation);
        this.saturationValue = saturation;
        if (!this.isStreaming) {
            this.restoreUndo();
        }
        var imageData = this.context.getImageData(0, 0, this.canvas.width, this.canvas.height);
        var dA = imageData.data;
        var sv = saturation;
        var luR = 0.3086;
        var luG = 0.6094;
        var luB = 0.0820;
        var az = (1 - sv) * luR + sv;
        var bz = (1 - sv) * luG;
        var cz = (1 - sv) * luB;
        var dz = (1 - sv) * luR;
        var ez = (1 - sv) * luG + sv;
        var fz = (1 - sv) * luB;
        var gz = (1 - sv) * luR;
        var hz = (1 - sv) * luG;
        var iz = (1 - sv) * luB + sv;
        for (var i = 0; i < dA.length; i += 4) {
            var red = dA[i];
            var green = dA[i + 1];
            var blue = dA[i + 2];
            var saturatedRed = (az * red + bz * green + cz * blue);
            var saturatedGreen = (dz * red + ez * green + fz * blue);
            var saturateddBlue = (gz * red + hz * green + iz * blue);
            dA[i] = saturatedRed;
            dA[i + 1] = saturatedGreen;
            dA[i + 2] = saturateddBlue;
        }
        imageData.data = dA;
        this.context.putImageData(imageData, 0, 0);
    };

    this.applyNoise = function (value) {
        var noise = 0;
        value = parseInt(value, 10);
        this.noiseValue = value;
        if (!this.isStreaming) {
            this.restoreUndo();
        }
        var imageData = this.context.getImageData(0, 0, this.canvas.width, this.canvas.height);
        var data = imageData.data;
        for (var i = 0; i < data.length; i += 4) {
            noise = value - Math.random() * value / 2;
            data[i] += noise;
            data[i + 1] += noise;
            data[i + 2] += noise;
        }
        imageData.data = data;
        this.context.putImageData(imageData, 0, 0);
    };

    this.applySharpen = function (mix) {
        var x = 0;
        this.restoreUndo();
        mix = parseFloat(mix);
        var w = this.canvas.width;
        var h = this.canvas.height;
        var weights = [0, -1, 0, -1, 5, -1, 0, -1, 0];
        var katet = Math.round(Math.sqrt(weights.length));
        var half = (katet * 0.5) | 0;
        var dstData = this.context.createImageData(w, h);
        var dstBuff = dstData.data;
        var srcBuff = this.context.getImageData(0, 0, w, h).data;
        var y = h;
        while (y--) {
            x = w;
            while (x--) {
                var sy = y,
                        sx = x,
                        dstOff = (y * w + x) * 4,
                        r = 0,
                        g = 0,
                        b = 0, a = 0;
                for (var cy = 0; cy < katet; cy++) {
                    for (var cx = 0; cx < katet; cx++) {
                        var scy = sy + cy - half;
                        var scx = sx + cx - half;
                        if (scy >= 0 && scy < h && scx >= 0 && scx < w) {
                            var srcOff = (scy * w + scx) * 4;
                            var wt = weights[cy * katet + cx];
                            r += srcBuff[srcOff] * wt;
                            g += srcBuff[srcOff + 1] * wt;
                            b += srcBuff[srcOff + 2] * wt;
                            a += srcBuff[srcOff + 3] * wt;
                        }
                    }
                }
                dstBuff[dstOff] = r * mix + srcBuff[dstOff] * (1 - mix);
                dstBuff[dstOff + 1] = g * mix + srcBuff[dstOff + 1] * (1 - mix);
                dstBuff[dstOff + 2] = b * mix + srcBuff[dstOff + 2] * (1 - mix);
                dstBuff[dstOff + 3] = srcBuff[dstOff + 3];
            }
        }
        this.context.putImageData(dstData, 0, 0);
    };

    this.applyVignette = function () {
        this.context.save();
        var gradient, outerRadius = Math.sqrt(Math.pow(this.canvas.width / 2, 2) + Math.pow(this.canvas.height / 2, 2));
        this.context.globalCompositeOperation = 'source-over';
        gradient = this.context.createRadialGradient(this.canvas.width / 2, this.canvas.height / 2, 0, this.canvas.width / 2, this.canvas.height / 2, outerRadius);
        gradient.addColorStop(0, 'rgba(0, 0, 0, 0)');
        gradient.addColorStop(0.5, 'rgba(0, 0, 0, 0.3)');
        gradient.addColorStop(1, 'rgba(0, 0, 0, 0.6)');
        this.context.fillStyle = gradient;
        this.context.fillRect(0, 0, this.canvas.width, this.canvas.height);
        this.context.globalCompositeOperation = 'lighter';
        gradient = this.context.createRadialGradient(this.canvas.width / 2, this.canvas.height / 2, 0, this.canvas.width / 2, this.canvas.height / 2, outerRadius);
        gradient.addColorStop(0, 'rgba(255, 255, 255, 0.1)');
        gradient.addColorStop(0.5, 'rgba(255, 255, 255, 0)');
        gradient.addColorStop(1, 'rgba(0, 0, 0, 0)');
        this.context.fillStyle = gradient;
        this.context.fillRect(0, 0, this.canvas.width, this.canvas.height);
        this.context.restore();
    };

    this.applyGrayscale = function () {
        this.effect = "Grayscale";
        if (!this.isStreaming) {
            this.restoreUndo();
        }
        var imageData = this.context.getImageData(0, 0, this.canvas.width, this.canvas.height);
        var dataArray = imageData.data;
        for (var i = 0; i < dataArray.length; i += 4) {
            var red = dataArray[i];
            var green = dataArray[i + 1];
            var blue = dataArray[i + 2];
            var alpha = dataArray[i + 3];
            var gray = (red + green + blue) / 3;
            dataArray[i] = gray;
            dataArray[i + 1] = gray;
            dataArray[i + 2] = gray;
            dataArray[i + 3] = alpha;
        }
        imageData.data = dataArray;
        this.context.putImageData(imageData, 0, 0);
    };

    this.applySepia = function () {
        this.effect = "Sepia";
        if (!this.isStreaming) {
            this.restoreUndo();
        }
        var imageData = this.context.getImageData(0, 0, this.canvas.width, this.canvas.height);
        var dataArray = imageData.data;
        for (var i = 0; i < dataArray.length; i += 4) {
            var red = dataArray[i];
            var green = dataArray[i + 1];
            var blue = dataArray[i + 2];
            var alpha = dataArray[i + 3];
            var outRed = (red * .393) + (green * .769) + (blue * .189);
            var outGreen = (red * .349) + (green * .686) + (blue * .168);
            var outBlue = (red * .272) + (green * .534) + (blue * .131);
            dataArray[i] = outRed < 255 ? outRed : 255;
            dataArray[i + 1] = outGreen < 255 ? outGreen : 255;
            dataArray[i + 2] = outBlue < 255 ? outBlue : 255;
            dataArray[i + 3] = alpha;
        }
        imageData.data = dataArray;
        this.context.putImageData(imageData, 0, 0);
    };

    this.applyInvert = function () {
        this.effect = "Invert";
        if (!this.isStreaming) {
            this.restoreUndo();
        }
        var imageData = this.context.getImageData(0, 0, this.canvas.width, this.canvas.height);
        var dataArr = imageData.data;
        for (var i = 0; i < dataArr.length; i += 4) {
            var r = dataArr[i];
            var g = dataArr[i + 1];
            var b = dataArr[i + 2];
            var a = dataArr[i + 3];
            var invertedRed = 255 - r;
            var invertedGreen = 255 - g;
            var invertedBlue = 255 - b;
            dataArr[i] = invertedRed;
            dataArr[i + 1] = invertedGreen;
            dataArr[i + 2] = invertedBlue;
        }
        imageData.data = dataArr;
        this.context.putImageData(imageData, 0, 0);
    };

    this.applyRed = function (value) {
        value = parseInt(value, 10);
        this.restoreUndo();
        var imageData = this.context.getImageData(0, 0, this.canvas.width, this.canvas.height);
        var data = imageData.data;
        for (var i = 0; i < data.length; i += 4) {
            data[i] = 255 - ((255 - data[i]) * (255 - value * 1) / 255);
        }
        imageData.data = data;
        this.context.putImageData(imageData, 0, 0);
    };

    this.applyGreen = function (value) {
        value = parseInt(value, 10);
        this.restoreUndo();
        var imageData = this.context.getImageData(0, 0, this.canvas.width, this.canvas.height);
        var data = imageData.data;
        for (var i = 0; i < data.length; i += 4) {
            data[i + 1] = 255 - ((255 - data[i + 1]) * (255 - value * 1) / 255);
        }
        imageData.data = data;
        this.context.putImageData(imageData, 0, 0);
    };

    this.applyBlue = function (value) {
        value = parseInt(value, 10);
        this.restoreUndo();
        var imageData = this.context.getImageData(0, 0, this.canvas.width, this.canvas.height);
        var data = imageData.data;
        for (var i = 0; i < data.length; i += 4) {
            data[i + 2] = 255 - ((255 - data[i + 2]) * (255 - value * 1) / 255);
        }
        imageData.data = data;
        this.context.putImageData(imageData, 0, 0);
    };

    this.applyCrop = function (value) {
        this.context.save();
        this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
        value = parseInt(value, 10);
        var sourceX = 0;
        var sourceY = 0;
        if (value > 0) {
            sourceX = this.tempCanvas.width / value;
            sourceY = this.tempCanvas.height / value;
        }
        var sourceWidth = this.tempCanvas.width - sourceX * 2;
        var sourceHeight = this.tempCanvas.height - sourceY * 2;
        var destX = 0;
        var destY = 0;
        var destWidth = this.canvas.width;
        var destHeight = this.canvas.height;
        this.context.drawImage(this.tempCanvas, sourceX, sourceY, sourceWidth, sourceHeight, destX, destY, destWidth, destHeight);
        this.context.restore();
    };

    this.applyFrame = function (frame) {
        var self = this;
        this.restoreUndo();
        var imageData = this.context.getImageData(0, 0, this.canvas.width, this.canvas.height);
        var data = imageData.data;
        var mask = new Image();
        mask.src = "img/frames/" + frame + ".jpg";
        var maskData;
        mask.onload = function () {
            var tempCanvas = document.createElement('canvas');
            var width = self.canvas.width;
            var height = self.canvas.height;
            tempCanvas.width = width;
            tempCanvas.height = height;
            var tempContext = tempCanvas.getContext("2d");
            tempContext.drawImage(mask, 0, 0, mask.width, mask.height, 0, 0, width, height);
            var tempData = tempContext.getImageData(0, 0, tempCanvas.width, tempCanvas.height);
            maskData = tempData.data;
            for (var i = 0; i < data.length; i += 4) {
                data[i] = data[i] * maskData[i] / 255;
                data[i + 1] = data[i + 1] * maskData[i + 1] / 255;
                data[i + 2] = data[i + 2] * maskData[i + 2] / 255;
            }
            imageData.data = data;
            self.context.putImageData(imageData, 0, 0);
        };
    };

    this.applyBackground = function (background) {
        var self = this;
        this.restoreUndo();
        var imageData = this.context.getImageData(0, 0, this.canvas.width, this.canvas.height);
        var data = imageData.data;
        var mask = new Image();
        mask.src = "img/backgrounds/" + background + ".jpg";
        var maskData;
        mask.onload = function () {
            var tempCanvas = document.createElement('canvas');
            var width = self.canvas.width;
            var height = self.canvas.height;
            tempCanvas.width = width;
            tempCanvas.height = height;
            var tempContext = tempCanvas.getContext("2d");
            tempContext.drawImage(mask, 0, 0, mask.width, mask.height, 0, 0, width, height);
            var tempData = tempContext.getImageData(0, 0, tempCanvas.width, tempCanvas.height);
            maskData = tempData.data;
            for (var i = 0; i < data.length; i += 4) {
                data[i] = data[i] * maskData[i] / 255;
                data[i + 1] = data[i + 1] * maskData[i + 1] / 255;
                data[i + 2] = data[i + 2] * maskData[i + 2] / 255;
            }
            imageData.data = data;
            self.context.putImageData(imageData, 0, 0);
        };
    };

    this.ucwords = function (str) {
        return (str + '').replace(/^([a-z])|\s+([a-z])/g, function ($1) {
            return $1.toUpperCase();
        });
    };

    return this.__constructor();
};

new PicozuExpress();
