package mara.mybox.image;

import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import javafx.embed.swing.SwingFXUtils;
import javafx.scene.image.Image;
import mara.mybox.data.IntPoint;
import static mara.mybox.value.AppVariables.logger;
import mara.mybox.value.CommonFxValues;

/**
 * @Author Mara
 * @CreateDate 2019-2-13 14:44:03
 * @Version 1.0
 * @Description Pixel operations whose calculation only involves pixel itself.
 * Pixel operations who involve other pixels need defined separatedly.
 * @License Apache License Version 2.0
 */
public class PixelsOperation {

    protected BufferedImage image;
    protected boolean isDithering, boolPara, skipTransparent = true;
    protected int intPara1, intPara2, intPara3;
    protected float floatPara1, floatPara2;
    protected Color colorPara1, colorPara2, bkColor;
    protected ImageScope scope;
    protected OperationType operationType;
    protected ColorActionType colorActionType;

    protected Color[] thisLine, nextLine;
    protected int thisLineY;
    protected int imageWidth, imageHeight;

    public enum OperationType {
        Smooth, Denoise, Blur, Sharpen, Clarity, Emboss, EdgeDetect,
        Thresholding, Quantization, Gray, BlackOrWhite, Sepia,
        ReplaceColor, Invert, Red, Green, Blue, Yellow, Cyan, Magenta, Mosaic,
        FrostedGlass,
        Brightness, Saturation, Hue, Opacity, PreOpacity, RGB, Color, ShowScope,
        Convolution, Contrast
    }

    public enum ColorActionType {
        Increase, Decrease, Set, Filter, Invert
    }

    public PixelsOperation() {
        this.bkColor = ImageColor.getAlphaColor();

    }

    public PixelsOperation(BufferedImage image, ImageScope scope, OperationType operationType) {
        this.image = image;
        this.operationType = operationType;
        this.scope = scope;
    }

    public static PixelsOperation create(Image image,
            ImageScope scope, OperationType operationType) {
        BufferedImage bufferedImage = SwingFXUtils.fromFXImage(image, null);
        return PixelsOperation.create(bufferedImage, scope, operationType);
    }

    public static PixelsOperation create(Image image,
            ImageScope scope, OperationType operationType, ColorActionType colorActionType) {
        BufferedImage bufferedImage = SwingFXUtils.fromFXImage(image, null);
        return PixelsOperation.create(bufferedImage, scope, operationType, colorActionType);
    }

    public static PixelsOperation create(BufferedImage image,
            ImageScope scope, OperationType operationType) {
        switch (operationType) {
            case ShowScope:
                return new ShowScope(image, scope);
            case Sepia:
                return new Sepia(image, scope);
            case Thresholding:
                return new Thresholding(image, scope);
            default:
                return new PixelsOperation(image, scope, operationType);
        }
    }

    public static PixelsOperation create(BufferedImage image,
            ImageScope scope, OperationType operationType, ColorActionType colorActionType) {
        switch (operationType) {
            case ReplaceColor:
                return new ReplaceColor(image, scope);
            case Color:
                return new SetColor(image, scope);
            case Opacity:
                switch (colorActionType) {
                    case Increase:
                        return new IncreaseOpacity(image, scope);
                    case Decrease:
                        return new DecreaseOpacity(image, scope);
                    case Set:
                    default:
                        return new SetOpacity(image, scope);
                }
            case PreOpacity:
                switch (colorActionType) {
                    case Increase:
                        return new IncreasePreOpacity(image, scope);
                    case Decrease:
                        return new DecreasePreOpacity(image, scope);
                    case Set:
                    default:
                        return new SetPreOpacity(image, scope);
                }
            case Brightness:
                switch (colorActionType) {
                    case Increase:
                        return new IncreaseBrightness(image, scope);
                    case Decrease:
                        return new DecreaseBrightness(image, scope);
                    case Set:
                    default:
                        return new SetBrightness(image, scope);
                }
            case Saturation:
                switch (colorActionType) {
                    case Increase:
                        return new IncreaseSaturation(image, scope);
                    case Decrease:
                        return new DecreaseSaturation(image, scope);
                    case Set:
                    default:
                        return new SetSaturation(image, scope);
                }
            case Hue:
                switch (colorActionType) {
                    case Increase:
                        return new IncreaseHue(image, scope);
                    case Decrease:
                        return new DecreaseHue(image, scope);
                    case Set:
                    default:
                        return new SetHue(image, scope);
                }
            case Red:
                switch (colorActionType) {
                    case Increase:
                        return new IncreaseRed(image, scope);
                    case Decrease:
                        return new DecreaseRed(image, scope);
                    case Filter:
                        return new FilterRed(image, scope);
                    case Invert:
                        return new InvertRed(image, scope);
                    case Set:
                    default:
                        return new SetRed(image, scope);
                }
            case Green:
                switch (colorActionType) {
                    case Increase:
                        return new IncreaseGreen(image, scope);
                    case Decrease:
                        return new DecreaseGreen(image, scope);
                    case Filter:
                        return new FilterGreen(image, scope);
                    case Invert:
                        return new InvertGreen(image, scope);
                    case Set:
                    default:
                        return new SetGreen(image, scope);
                }
            case Blue:
                switch (colorActionType) {
                    case Increase:
                        return new IncreaseBlue(image, scope);
                    case Decrease:
                        return new DecreaseBlue(image, scope);
                    case Filter:
                        return new FilterBlue(image, scope);
                    case Invert:
                        return new InvertBlue(image, scope);
                    case Set:
                    default:
                        return new SetBlue(image, scope);
                }
            case Yellow:
                switch (colorActionType) {
                    case Increase:
                        return new IncreaseYellow(image, scope);
                    case Decrease:
                        return new DecreaseYellow(image, scope);
                    case Filter:
                        return new FilterYellow(image, scope);
                    case Invert:
                        return new InvertYellow(image, scope);
                    case Set:
                    default:
                        return new SetYellow(image, scope);
                }
            case Cyan:
                switch (colorActionType) {
                    case Increase:
                        return new IncreaseCyan(image, scope);
                    case Decrease:
                        return new DecreaseCyan(image, scope);
                    case Filter:
                        return new FilterCyan(image, scope);
                    case Invert:
                        return new InvertCyan(image, scope);
                    case Set:
                    default:
                        return new SetCyan(image, scope);
                }
            case Magenta:
                switch (colorActionType) {
                    case Increase:
                        return new IncreaseMagenta(image, scope);
                    case Decrease:
                        return new DecreaseMagenta(image, scope);
                    case Filter:
                        return new FilterMagenta(image, scope);
                    case Invert:
                        return new InvertMagenta(image, scope);
                    case Set:
                    default:
                        return new SetMagenta(image, scope);
                }
            case RGB:
                switch (colorActionType) {
                    case Increase:
                        return new IncreaseRGB(image, scope);
                    case Decrease:
                        return new DecreaseRGB(image, scope);
                    case Invert:
                        return new InvertRGB(image, scope);
//                    case Set:
//                    default:
//                        return new SetRGB(image, scope);
                }
            default:
                return new PixelsOperation(image, scope, operationType);
        }
    }

    public BufferedImage operate() {
        return operateImage();
    }

    public BufferedImage operateImage() {
        if (image == null || operationType == null) {
            return image;
        }
        imageWidth = image.getWidth();
        imageHeight = image.getHeight();
        if (operationType != OperationType.BlackOrWhite
                && operationType != OperationType.Quantization) {
            isDithering = false;
        }
        scope = ImageScope.fineImageScope(scope);
        skipTransparent = operationType != OperationType.ReplaceColor
                || colorPara1.getRGB() != 0;

        if (scope != null && scope.getScopeType() == ImageScope.ScopeType.Matting) {
            isDithering = false;
            return operateMatting();
        } else {
            return operateScope();
        }
    }

    public Image operateFxImage() {
        BufferedImage target = operate();
        if (target == null) {
            return null;
        }
        return SwingFXUtils.toFXImage(target, null);
    }

    protected BufferedImage operateScope() {
        if (image == null) {
            return image;
        }
        try {
            int imageType = image.getType();
            if (imageType == BufferedImage.TYPE_CUSTOM) {
                imageType = BufferedImage.TYPE_INT_ARGB;
            }
            BufferedImage target = new BufferedImage(imageWidth, imageHeight, imageType);
            boolean isShowScope = (operationType == OperationType.ShowScope);
            boolean isWhole = (scope == null || scope.getScopeType() == ImageScope.ScopeType.All);
            boolean inScope;
            if (isDithering) {
                thisLine = new Color[imageWidth];
                nextLine = new Color[imageWidth];
                thisLineY = 0;
                thisLine[0] = new Color(image.getRGB(0, 0), true);
            }
            Color newColor;
            int pixel, transparent = 0, white = Color.WHITE.getRGB();
            for (int y = 0; y < image.getHeight(); y++) {
                for (int x = 0; x < image.getWidth(); x++) {
                    pixel = image.getRGB(x, y);
                    Color color = new Color(pixel, true);
                    if (pixel == 0 && skipTransparent) {  // pass transparency
                        newColor = color;

                    } else {

                        inScope = isWhole || scope.inScope(x, y, color);

                        if (isDithering && y == thisLineY) {
                            color = thisLine[x];
                        }
                        if (isShowScope) {
                            newColor = color;
                            if (inScope) {
                                target.setRGB(x, y, transparent);
                            } else {
                                target.setRGB(x, y, white);
                            }
                        } else {
                            if (inScope) {
                                newColor = operatePixel(target, color, x, y);

                            } else {
                                newColor = color;
                                target.setRGB(x, y, color.getRGB());
                            }
                        }
                    }
                    dithering(color, newColor, x, y);
                }
                if (isDithering) {
                    thisLine = nextLine;
                    thisLineY = y + 1;
                    nextLine = new Color[imageWidth];
                }
            }
            thisLine = nextLine = null;
            return target;
        } catch (Exception e) {
            logger.error(e.toString());
            return image;
        }
    }

    // https://en.wikipedia.org/wiki/Flood_fill
    // https://www.codeproject.com/Articles/6017/QuickFill-An-Efficient-Flood-Fill-Algorithm
    protected BufferedImage operateMatting() {
        try {
            if (image == null || scope == null) {
                return image;
            }
            boolean isShowScope = operationType == OperationType.ShowScope;
            int imageType = image.getType();
            if (imageType == BufferedImage.TYPE_CUSTOM) {
                imageType = BufferedImage.TYPE_INT_ARGB;
            }
            BufferedImage target = new BufferedImage(imageWidth, imageHeight, imageType);
            boolean excluded = scope.isColorExcluded();
            if (isShowScope) {
                if (excluded) {
                    Graphics2D g2d = target.createGraphics();
                    g2d.setColor(CommonFxValues.TRANSPARENT);
                    g2d.fillRect(0, 0, imageWidth, imageHeight);
                    g2d.dispose();
                } else {
                    Graphics2D g2d = target.createGraphics();
                    g2d.setColor(Color.WHITE);
                    g2d.fillRect(0, 0, imageWidth, imageHeight);
                    g2d.dispose();
                }

            } else {
                if (excluded) {
                    for (int y = 0; y < imageHeight; y++) {
                        for (int x = 0; x < imageWidth; x++) {
                            operatePixel(target, x, y);
                        }
                    }
                } else {
                    target = image.getSubimage(0, 0, imageWidth, imageHeight);
                }
            }
            List<IntPoint> points = scope.getPoints();
            if (points == null || points.isEmpty()) {
                return target;
            }

            boolean[][] visited = new boolean[imageHeight][imageWidth];
            Queue<IntPoint> queue = new LinkedList<>();
            int transpaernt = 0, white = Color.WHITE.getRGB();
            boolean eightNeighbor = scope.isEightNeighbor();
            for (IntPoint point : points) {
                Color startColor = new Color(image.getRGB(point.getX(), point.getY()), true);
                queue.add(point);
                while (!queue.isEmpty()) {
                    IntPoint p = queue.remove();
                    int x = p.getX(), y = p.getY();
                    if (x < 0 || x >= imageWidth || y < 0 || y >= imageHeight
                            || visited[y][x]) {
                        continue;
                    }
                    visited[y][x] = true;
                    int pixel = image.getRGB(x, y);
                    Color color = new Color(pixel, true);
                    if (scope.inColorMatch(startColor, color)) {
                        if (pixel == 0 && skipTransparent) {
                            target.setRGB(x, y, pixel);
                        } else if (isShowScope) {
                            if (excluded) {
                                target.setRGB(x, y, white);
                            } else {
                                target.setRGB(x, y, transpaernt);
                            }
                        } else {
                            if (excluded) {
                                target.setRGB(x, y, pixel);
                            } else {
                                operatePixel(target, color, x, y);
                            }
                        }

                        queue.add(new IntPoint(x + 1, y));
                        queue.add(new IntPoint(x - 1, y));
                        queue.add(new IntPoint(x, y + 1));
                        queue.add(new IntPoint(x, y - 1));

                        if (eightNeighbor) {
                            queue.add(new IntPoint(x + 1, y + 1));
                            queue.add(new IntPoint(x + 1, y - 1));
                            queue.add(new IntPoint(x - 1, y + 1));
                            queue.add(new IntPoint(x - 1, y - 1));
                        }
                    }
                }
            }
            return target;
        } catch (Exception e) {
            logger.error(e.toString());
            return null;
        }
    }

    protected Color operatePixel(BufferedImage target, int x, int y) {
        int pixel = image.getRGB(x, y);
        Color color = new Color(pixel, true);
        if (pixel == 0 && skipTransparent) {
            return color;
        } else {
            return operatePixel(target, color, x, y);
        }
    }

    protected Color operatePixel(BufferedImage target, Color color, int x, int y) {
        Color newColor = operateColor(color);
        target.setRGB(x, y, newColor.getRGB());
        return newColor;
    }

    // https://en.wikipedia.org/wiki/Dither
    // https://en.wikipedia.org/wiki/Floyd%E2%80%93Steinberg_dithering
    protected void dithering(Color color, Color newColor, int x, int y) {
        if (!isDithering || y != thisLineY) {
            return;
        }
        int red_error, green_error, blue_error;
        int new_red, new_green, new_blue;
        red_error = color.getRed() - newColor.getRed();
        green_error = color.getGreen() - newColor.getGreen();
        blue_error = color.getBlue() - newColor.getBlue();

        if (x + 1 < imageWidth) {
            color = new Color(image.getRGB(x + 1, y), true);
            new_red = Math.max(Math.min(color.getRed() + red_error * 7 / 16, 255), 0);
            new_green = Math.max(Math.min(color.getGreen() + green_error * 7 / 16, 255), 0);
            new_blue = Math.max(Math.min(color.getBlue() + blue_error * 7 / 16, 255), 0);
            newColor = new Color(new_red, new_green, new_blue, color.getAlpha());
            thisLine[x + 1] = newColor;
        }

        if (x - 1 >= 0 && y + 1 < imageHeight) {
            color = new Color(image.getRGB(x - 1, y + 1), true);
            new_red = Math.max(Math.min(color.getRed() + red_error * 3 / 16, 255), 0);
            new_green = Math.max(Math.min(color.getGreen() + green_error * 3 / 16, 255), 0);
            new_blue = Math.max(Math.min(color.getBlue() + blue_error * 3 / 16, 255), 0);
            newColor = new Color(new_red, new_green, new_blue, color.getAlpha());
            nextLine[x - 1] = newColor;
        }

        if (y + 1 < imageHeight) {
            color = new Color(image.getRGB(x, y + 1), true);
            new_red = Math.max(Math.min(color.getRed() + red_error * 5 / 16, 255), 0);
            new_green = Math.max(Math.min(color.getGreen() + green_error * 5 / 16, 255), 0);
            new_blue = Math.max(Math.min(color.getBlue() + blue_error * 5 / 16, 255), 0);
            newColor = new Color(new_red, new_green, new_blue, color.getAlpha());
            nextLine[x] = newColor;
        }

        if (x + 1 < imageWidth && y + 1 < imageHeight) {
            color = new Color(image.getRGB(x + 1, y + 1), true);
            new_red = Math.max(Math.min(color.getRed() + red_error * 1 / 16, 255), 0);
            new_green = Math.max(Math.min(color.getGreen() + green_error * 1 / 16, 255), 0);
            new_blue = Math.max(Math.min(color.getBlue() + blue_error * 1 / 16, 255), 0);
            newColor = new Color(new_red, new_green, new_blue, color.getAlpha());
            nextLine[x + 1] = newColor;
        }

    }

    // SubClass should implement this
    protected Color operateColor(Color color) {
        return color;
    }

    public static class ShowScope extends PixelsOperation {

        public ShowScope(BufferedImage image, ImageScope scope) {
            this.operationType = OperationType.ShowScope;
            this.image = image;
            this.scope = scope;
        }

        @Override
        protected Color operateColor(Color color) {
            return new Color(color.getRed(), color.getGreen(), color.getBlue(),
                    (int) (scope.getOpacity() * 255));
        }
    }

    public static class Sepia extends PixelsOperation {

        public Sepia(BufferedImage image, ImageScope scope) {
            this.operationType = OperationType.Sepia;
            this.image = image;
            this.scope = scope;
        }

        @Override
        protected Color operateColor(Color color) {
            return ImageColor.pixel2Sepia(color, intPara1);
        }
    }

    public static class Thresholding extends PixelsOperation {

        public Thresholding(BufferedImage image, ImageScope scope) {
            this.operationType = OperationType.Thresholding;
            this.image = image;
            this.scope = scope;
        }

        @Override
        protected Color operateColor(Color color) {
            return ImageColor.thresholdingColor(color, intPara1, intPara2, intPara3);
        }
    }

    public static class ReplaceColor extends PixelsOperation {

        public ReplaceColor(BufferedImage image, ImageScope scope) {
            this.operationType = OperationType.ReplaceColor;
            this.colorActionType = ColorActionType.Set;
            this.image = image;
            this.scope = scope;
        }

        @Override
        protected Color operateColor(Color color) {
            return colorPara2;
        }
    }

    public static class SetColor extends PixelsOperation {

        public SetColor(BufferedImage image, ImageScope scope) {
            this.operationType = OperationType.Color;
            this.image = image;
            this.scope = scope;
        }

        @Override
        protected Color operateColor(Color color) {
            return colorPara1;
        }
    }

    public static class SetOpacity extends PixelsOperation {

        public SetOpacity(BufferedImage image, ImageScope scope) {
            this.operationType = OperationType.Opacity;
            this.colorActionType = ColorActionType.Set;
            this.image = image;
            this.scope = scope;
        }

        @Override
        protected Color operateColor(Color color) {
            return new Color(color.getRed(), color.getGreen(), color.getBlue(),
                    Math.min(Math.max(intPara1, 0), 255));
        }
    }

    public static class IncreaseOpacity extends PixelsOperation {

        public IncreaseOpacity(BufferedImage image, ImageScope scope) {
            this.operationType = OperationType.Opacity;
            this.colorActionType = ColorActionType.Increase;
            this.image = image;
            this.scope = scope;
        }

        @Override
        protected Color operateColor(Color color) {
            return new Color(color.getRed(), color.getGreen(), color.getBlue(),
                    Math.min(Math.max(color.getAlpha() + intPara1, 0), 255));
        }
    }

    public static class DecreaseOpacity extends PixelsOperation {

        public DecreaseOpacity(BufferedImage image, ImageScope scope) {
            this.operationType = OperationType.Opacity;
            this.colorActionType = ColorActionType.Decrease;
            this.image = image;
            this.scope = scope;
        }

        @Override
        protected Color operateColor(Color color) {
            return new Color(color.getRed(), color.getGreen(), color.getBlue(),
                    Math.min(Math.max(color.getAlpha() - intPara1, 0), 255));
        }
    }

    public static class SetPreOpacity extends PixelsOperation {

        public SetPreOpacity(BufferedImage image, ImageScope scope) {
            this.operationType = OperationType.PreOpacity;
            this.colorActionType = ColorActionType.Set;
            this.image = image;
            this.scope = scope;
        }

        @Override
        protected Color operateColor(Color color) {
            int opacity = Math.min(Math.max(intPara1, 0), 255);
            float f = opacity / 255.0f;
            return ImageColor.blendAlpha(color, f, bkColor);
        }
    }

    public static class IncreasePreOpacity extends PixelsOperation {

        public IncreasePreOpacity(BufferedImage image, ImageScope scope) {
            this.operationType = OperationType.PreOpacity;
            this.colorActionType = ColorActionType.Increase;
            this.image = image;
            this.scope = scope;
        }

        @Override
        protected Color operateColor(Color color) {
            int opacity = Math.min(Math.max(color.getAlpha() + intPara1, 0), 255);
            float f = opacity / 255.0f;
            return ImageColor.blendAlpha(color, f, bkColor);
        }
    }

    public static class DecreasePreOpacity extends PixelsOperation {

        public DecreasePreOpacity(BufferedImage image, ImageScope scope) {
            this.operationType = OperationType.PreOpacity;
            this.colorActionType = ColorActionType.Decrease;
            this.image = image;
            this.scope = scope;
        }

        @Override
        protected Color operateColor(Color color) {
            int opacity = Math.min(Math.max(color.getAlpha() - intPara1, 0), 255);
            float f = opacity / 255.0f;
            return ImageColor.blendAlpha(color, f, bkColor);
        }
    }

    public static class SetBrightness extends PixelsOperation {

        public SetBrightness(BufferedImage image, ImageScope scope) {
            this.operationType = OperationType.Brightness;
            this.colorActionType = ColorActionType.Set;
            this.image = image;
            this.scope = scope;
        }

        @Override
        protected Color operateColor(Color color) {
            float[] hsb;
            hsb = ImageColor.getHSB(color);
            float f = floatPara1;
            f = Math.min(Math.max(f, 0.0f), 1.0f);
            return ImageColor.HSB2RGB(hsb[0], hsb[1], f);
        }
    }

    public static class IncreaseBrightness extends PixelsOperation {

        public IncreaseBrightness(BufferedImage image, ImageScope scope) {
            this.operationType = OperationType.Brightness;
            this.colorActionType = ColorActionType.Increase;
            this.image = image;
            this.scope = scope;
        }

        @Override
        protected Color operateColor(Color color) {
            float[] hsb;
            hsb = ImageColor.getHSB(color);
            float f = hsb[2] * (1.0f + floatPara1);
            f = Math.min(Math.max(f, 0.0f), 1.0f);
            return ImageColor.HSB2RGB(hsb[0], hsb[1], f);
        }
    }

    public static class DecreaseBrightness extends PixelsOperation {

        public DecreaseBrightness(BufferedImage image, ImageScope scope) {
            this.operationType = OperationType.Brightness;
            this.colorActionType = ColorActionType.Decrease;
            this.image = image;
            this.scope = scope;
        }

        @Override
        protected Color operateColor(Color color) {
            float[] hsb;
            hsb = ImageColor.getHSB(color);
            float f = hsb[2] * (1.0f - floatPara1);
            f = Math.min(Math.max(f, 0.0f), 1.0f);
            return ImageColor.HSB2RGB(hsb[0], hsb[1], f);
        }
    }

    public static class SetSaturation extends PixelsOperation {

        public SetSaturation(BufferedImage image, ImageScope scope) {
            this.operationType = OperationType.Saturation;
            this.colorActionType = ColorActionType.Set;
            this.image = image;
            this.scope = scope;
        }

        @Override
        protected Color operateColor(Color color) {
            float[] hsb;
            hsb = ImageColor.getHSB(color);
            float f = floatPara1;
            f = Math.min(Math.max(f, 0.0f), 1.0f);
            return ImageColor.HSB2RGB(hsb[0], f, hsb[2]);
        }
    }

    public static class IncreaseSaturation extends PixelsOperation {

        public IncreaseSaturation(BufferedImage image, ImageScope scope) {
            this.operationType = OperationType.Saturation;
            this.colorActionType = ColorActionType.Increase;
            this.image = image;
            this.scope = scope;
        }

        @Override
        protected Color operateColor(Color color) {
            float[] hsb;
            hsb = ImageColor.getHSB(color);
            float f = hsb[1] * (1.0f + floatPara1);
            f = Math.min(Math.max(f, 0.0f), 1.0f);
            return ImageColor.HSB2RGB(hsb[0], f, hsb[2]);
        }
    }

    public static class DecreaseSaturation extends PixelsOperation {

        public DecreaseSaturation(BufferedImage image, ImageScope scope) {
            this.operationType = OperationType.Saturation;
            this.colorActionType = ColorActionType.Decrease;
            this.image = image;
            this.scope = scope;
        }

        @Override
        protected Color operateColor(Color color) {
            float[] hsb;
            hsb = ImageColor.getHSB(color);
            float f = hsb[1] * (1.0f - floatPara1);
            f = Math.min(Math.max(f, 0.0f), 1.0f);
            return ImageColor.HSB2RGB(hsb[0], f, hsb[2]);
        }
    }

    public static class SetHue extends PixelsOperation {

        public SetHue(BufferedImage image, ImageScope scope) {
            this.operationType = OperationType.Hue;
            this.colorActionType = ColorActionType.Set;
            this.image = image;
            this.scope = scope;
        }

        @Override
        protected Color operateColor(Color color) {
            float[] hsb;
            hsb = ImageColor.getHSB(color);
            float f = floatPara1;
            if (f > 1.0f) {
                f = f - 1.0f;
            }
            if (f < 0.0f) {
                f = f + 1.0f;
            }
            f = Math.min(Math.max(f, 0.0f), 1.0f);
            return ImageColor.HSB2RGB(f, hsb[1], hsb[2]);
        }
    }

    public static class IncreaseHue extends PixelsOperation {

        public IncreaseHue(BufferedImage image, ImageScope scope) {
            this.operationType = OperationType.Hue;
            this.colorActionType = ColorActionType.Increase;
            this.image = image;
            this.scope = scope;
        }

        @Override
        protected Color operateColor(Color color) {
            float[] hsb;
            hsb = ImageColor.getHSB(color);
            float f = hsb[0] + floatPara1;
            if (f > 1.0f) {
                f = f - 1.0f;
            }
            if (f < 0.0f) {
                f = f + 1.0f;
            }
            f = Math.min(Math.max(f, 0.0f), 1.0f);
            return ImageColor.HSB2RGB(f, hsb[1], hsb[2]);
        }
    }

    public static class DecreaseHue extends PixelsOperation {

        public DecreaseHue(BufferedImage image, ImageScope scope) {
            this.operationType = OperationType.Hue;
            this.colorActionType = ColorActionType.Decrease;
            this.image = image;
            this.scope = scope;
        }

        @Override
        protected Color operateColor(Color color) {
            float[] hsb;
            hsb = ImageColor.getHSB(color);
            float f = hsb[0] - floatPara1;
            if (f > 1.0f) {
                f = f - 1.0f;
            }
            if (f < 0.0f) {
                f = f + 1.0f;
            }
            f = Math.min(Math.max(f, 0.0f), 1.0f);
            return ImageColor.HSB2RGB(f, hsb[1], hsb[2]);
        }
    }

    public static class SetRed extends PixelsOperation {

        public SetRed(BufferedImage image, ImageScope scope) {
            this.operationType = OperationType.Red;
            this.colorActionType = ColorActionType.Set;
            this.image = image;
            this.scope = scope;
        }

        @Override
        protected Color operateColor(Color color) {
            return new Color(Math.min(Math.max(intPara1, 0), 255),
                    color.getGreen(), color.getBlue(), color.getAlpha());
        }
    }

    public static class IncreaseRed extends PixelsOperation {

        public IncreaseRed(BufferedImage image, ImageScope scope) {
            this.operationType = OperationType.Red;
            this.colorActionType = ColorActionType.Increase;
            this.image = image;
            this.scope = scope;
        }

        @Override
        protected Color operateColor(Color color) {
            return new Color(Math.min(Math.max(color.getRed() + intPara1, 0), 255),
                    color.getGreen(), color.getBlue(), color.getAlpha());
        }
    }

    public static class DecreaseRed extends PixelsOperation {

        public DecreaseRed(BufferedImage image, ImageScope scope) {
            this.operationType = OperationType.Red;
            this.colorActionType = ColorActionType.Decrease;
            this.image = image;
            this.scope = scope;
        }

        @Override
        protected Color operateColor(Color color) {
            return new Color(Math.min(Math.max(color.getRed() - intPara1, 0), 255),
                    color.getGreen(), color.getBlue(), color.getAlpha());
        }
    }

    public static class FilterRed extends PixelsOperation {

        public FilterRed(BufferedImage image, ImageScope scope) {
            this.operationType = OperationType.Red;
            this.colorActionType = ColorActionType.Filter;
            this.image = image;
            this.scope = scope;
        }

        @Override
        protected Color operateColor(Color color) {
            return new Color(color.getRed(), 0, 0, color.getAlpha());
        }
    }

    public static class InvertRed extends PixelsOperation {

        public InvertRed(BufferedImage image, ImageScope scope) {
            this.operationType = OperationType.Red;
            this.colorActionType = ColorActionType.Invert;
            this.image = image;
            this.scope = scope;
        }

        @Override
        protected Color operateColor(Color color) {
            return new Color(255 - color.getRed(), color.getGreen(), color.getBlue(), color.getAlpha());
        }
    }

    public static class SetGreen extends PixelsOperation {

        public SetGreen(BufferedImage image, ImageScope scope) {
            this.operationType = OperationType.Green;
            this.colorActionType = ColorActionType.Set;
            this.image = image;
            this.scope = scope;
        }

        @Override
        protected Color operateColor(Color color) {
            return new Color(color.getRed(), Math.min(Math.max(intPara1, 0), 255),
                    color.getBlue(), color.getAlpha());
        }
    }

    public static class IncreaseGreen extends PixelsOperation {

        public IncreaseGreen(BufferedImage image, ImageScope scope) {
            this.operationType = OperationType.Green;
            this.colorActionType = ColorActionType.Increase;
            this.image = image;
            this.scope = scope;
        }

        @Override
        protected Color operateColor(Color color) {
            return new Color(color.getRed(), Math.min(Math.max(color.getGreen() + intPara1, 0), 255),
                    color.getBlue(), color.getAlpha());
        }
    }

    public static class DecreaseGreen extends PixelsOperation {

        public DecreaseGreen(BufferedImage image, ImageScope scope) {
            this.operationType = OperationType.Green;
            this.colorActionType = ColorActionType.Decrease;
            this.image = image;
            this.scope = scope;
        }

        @Override
        protected Color operateColor(Color color) {
            return new Color(color.getRed(), Math.min(Math.max(color.getGreen() - intPara1, 0), 255),
                    color.getBlue(), color.getAlpha());
        }
    }

    public static class FilterGreen extends PixelsOperation {

        public FilterGreen(BufferedImage image, ImageScope scope) {
            this.operationType = OperationType.Green;
            this.colorActionType = ColorActionType.Filter;
            this.image = image;
            this.scope = scope;
        }

        @Override
        protected Color operateColor(Color color) {
            return new Color(0, color.getGreen(), 0, color.getAlpha());
        }
    }

    public static class InvertGreen extends PixelsOperation {

        public InvertGreen(BufferedImage image, ImageScope scope) {
            this.operationType = OperationType.Green;
            this.colorActionType = ColorActionType.Invert;
            this.image = image;
            this.scope = scope;
        }

        @Override
        protected Color operateColor(Color color) {
            return new Color(color.getRed(), 255 - color.getGreen(), color.getBlue(), color.getAlpha());
        }
    }

    public static class SetBlue extends PixelsOperation {

        public SetBlue(BufferedImage image, ImageScope scope) {
            this.operationType = OperationType.Blue;
            this.colorActionType = ColorActionType.Set;
            this.image = image;
            this.scope = scope;
        }

        @Override
        protected Color operateColor(Color color) {
            return new Color(color.getRed(), color.getGreen(),
                    Math.min(Math.max(intPara1, 0), 255), color.getAlpha());
        }
    }

    public static class IncreaseBlue extends PixelsOperation {

        public IncreaseBlue(BufferedImage image, ImageScope scope) {
            this.operationType = OperationType.Blue;
            this.colorActionType = ColorActionType.Increase;
            this.image = image;
            this.scope = scope;
        }

        @Override
        protected Color operateColor(Color color) {
            return new Color(color.getRed(), color.getGreen(),
                    Math.min(Math.max(color.getBlue() + intPara1, 0), 255), color.getAlpha());
        }
    }

    public static class DecreaseBlue extends PixelsOperation {

        public DecreaseBlue(BufferedImage image, ImageScope scope) {
            this.operationType = OperationType.Blue;
            this.colorActionType = ColorActionType.Decrease;
            this.image = image;
            this.scope = scope;
        }

        @Override
        protected Color operateColor(Color color) {
            return new Color(color.getRed(), color.getGreen(),
                    Math.min(Math.max(color.getBlue() - intPara1, 0), 255), color.getAlpha());
        }
    }

    public static class FilterBlue extends PixelsOperation {

        public FilterBlue(BufferedImage image, ImageScope scope) {
            this.operationType = OperationType.Blue;
            this.colorActionType = ColorActionType.Filter;
            this.image = image;
            this.scope = scope;
        }

        @Override
        protected Color operateColor(Color color) {
            return new Color(0, 0, color.getBlue(), color.getAlpha());
        }
    }

    public static class InvertBlue extends PixelsOperation {

        public InvertBlue(BufferedImage image, ImageScope scope) {
            this.operationType = OperationType.Blue;
            this.colorActionType = ColorActionType.Invert;
            this.image = image;
            this.scope = scope;
        }

        @Override
        protected Color operateColor(Color color) {
            return new Color(color.getRed(), color.getGreen(), 255 - color.getBlue(), color.getAlpha());
        }
    }

    public static class SetYellow extends PixelsOperation {

        public SetYellow(BufferedImage image, ImageScope scope) {
            this.operationType = OperationType.Yellow;
            this.colorActionType = ColorActionType.Set;
            this.image = image;
            this.scope = scope;
        }

        @Override
        protected Color operateColor(Color color) {
            int v = Math.min(Math.max(intPara1, 0), 255);
            return new Color(v, v, color.getBlue(), color.getAlpha());
        }
    }

    public static class IncreaseYellow extends PixelsOperation {

        public IncreaseYellow(BufferedImage image, ImageScope scope) {
            this.operationType = OperationType.Yellow;
            this.colorActionType = ColorActionType.Increase;
            this.image = image;
            this.scope = scope;
        }

        @Override
        protected Color operateColor(Color color) {
            return new Color(Math.min(Math.max(color.getRed() + intPara1, 0), 255),
                    Math.min(Math.max(color.getGreen() + intPara1, 0), 255),
                    color.getBlue(), color.getAlpha());
        }
    }

    public static class DecreaseYellow extends PixelsOperation {

        public DecreaseYellow(BufferedImage image, ImageScope scope) {
            this.operationType = OperationType.Yellow;
            this.colorActionType = ColorActionType.Decrease;
            this.image = image;
            this.scope = scope;
        }

        @Override
        protected Color operateColor(Color color) {
            return new Color(Math.min(Math.max(color.getRed() - intPara1, 0), 255),
                    Math.min(Math.max(color.getGreen() - intPara1, 0), 255),
                    color.getBlue(), color.getAlpha());
        }
    }

    public static class FilterYellow extends PixelsOperation {

        public FilterYellow(BufferedImage image, ImageScope scope) {
            this.operationType = OperationType.Yellow;
            this.colorActionType = ColorActionType.Filter;
            this.image = image;
            this.scope = scope;
        }

        @Override
        protected Color operateColor(Color color) {
            return new Color(color.getRed(), color.getGreen(), 0, color.getAlpha());
        }
    }

    public static class InvertYellow extends PixelsOperation {

        public InvertYellow(BufferedImage image, ImageScope scope) {
            this.operationType = OperationType.Yellow;
            this.colorActionType = ColorActionType.Invert;
            this.image = image;
            this.scope = scope;
        }

        @Override
        protected Color operateColor(Color color) {
            return new Color(255 - color.getRed(), 255 - color.getGreen(), color.getBlue(), color.getAlpha());
        }
    }

    public static class SetCyan extends PixelsOperation {

        public SetCyan(BufferedImage image, ImageScope scope) {
            this.operationType = OperationType.Cyan;
            this.colorActionType = ColorActionType.Set;
            this.image = image;
            this.scope = scope;
        }

        @Override
        protected Color operateColor(Color color) {
            int v = Math.min(Math.max(intPara1, 0), 255);
            return new Color(color.getRed(), v, v, color.getAlpha());
        }
    }

    public static class IncreaseCyan extends PixelsOperation {

        public IncreaseCyan(BufferedImage image, ImageScope scope) {
            this.operationType = OperationType.Cyan;
            this.colorActionType = ColorActionType.Increase;
            this.image = image;
            this.scope = scope;
        }

        @Override
        protected Color operateColor(Color color) {
            return new Color(color.getRed(),
                    Math.min(Math.max(color.getGreen() + intPara1, 0), 255),
                    Math.min(Math.max(color.getBlue() + intPara1, 0), 255),
                    color.getAlpha());
        }
    }

    public static class DecreaseCyan extends PixelsOperation {

        public DecreaseCyan(BufferedImage image, ImageScope scope) {
            this.operationType = OperationType.Cyan;
            this.colorActionType = ColorActionType.Decrease;
            this.image = image;
            this.scope = scope;
        }

        @Override
        protected Color operateColor(Color color) {
            return new Color(color.getRed(),
                    Math.min(Math.max(color.getGreen() - intPara1, 0), 255),
                    Math.min(Math.max(color.getBlue() - intPara1, 0), 255),
                    color.getAlpha());
        }
    }

    public static class FilterCyan extends PixelsOperation {

        public FilterCyan(BufferedImage image, ImageScope scope) {
            this.operationType = OperationType.Cyan;
            this.colorActionType = ColorActionType.Filter;
            this.image = image;
            this.scope = scope;
        }

        @Override
        protected Color operateColor(Color color) {
            return new Color(0, color.getGreen(), color.getBlue(), color.getAlpha());
        }
    }

    public static class InvertCyan extends PixelsOperation {

        public InvertCyan(BufferedImage image, ImageScope scope) {
            this.operationType = OperationType.Cyan;
            this.colorActionType = ColorActionType.Invert;
            this.image = image;
            this.scope = scope;
        }

        @Override
        protected Color operateColor(Color color) {
            return new Color(color.getRed(), 255 - color.getGreen(), 255 - color.getBlue(), color.getAlpha());
        }
    }

    public static class SetMagenta extends PixelsOperation {

        public SetMagenta(BufferedImage image, ImageScope scope) {
            this.operationType = OperationType.Magenta;
            this.colorActionType = ColorActionType.Set;
            this.image = image;
            this.scope = scope;
        }

        @Override
        protected Color operateColor(Color color) {
            int v = Math.min(Math.max(intPara1, 0), 255);
            return new Color(v, color.getGreen(), v, color.getAlpha());
        }
    }

    public static class IncreaseMagenta extends PixelsOperation {

        public IncreaseMagenta(BufferedImage image, ImageScope scope) {
            this.operationType = OperationType.Magenta;
            this.colorActionType = ColorActionType.Increase;
            this.image = image;
            this.scope = scope;
        }

        @Override
        protected Color operateColor(Color color) {
            return new Color(Math.min(Math.max(color.getRed() + intPara1, 0), 255),
                    color.getGreen(),
                    Math.min(Math.max(color.getBlue() + intPara1, 0), 255), color.getAlpha());
        }
    }

    public static class DecreaseMagenta extends PixelsOperation {

        public DecreaseMagenta(BufferedImage image, ImageScope scope) {
            this.operationType = OperationType.Magenta;
            this.colorActionType = ColorActionType.Decrease;
            this.image = image;
            this.scope = scope;
        }

        @Override
        protected Color operateColor(Color color) {
            return new Color(Math.min(Math.max(color.getRed() - intPara1, 0), 255),
                    color.getGreen(),
                    Math.min(Math.max(color.getBlue() - intPara1, 0), 255), color.getAlpha());
        }
    }

    public static class FilterMagenta extends PixelsOperation {

        public FilterMagenta(BufferedImage image, ImageScope scope) {
            this.operationType = OperationType.Magenta;
            this.colorActionType = ColorActionType.Filter;
            this.image = image;
            this.scope = scope;
        }

        @Override
        protected Color operateColor(Color color) {
            return new Color(color.getRed(), 0, color.getBlue(), color.getAlpha());
        }
    }

    public static class InvertMagenta extends PixelsOperation {

        public InvertMagenta(BufferedImage image, ImageScope scope) {
            this.operationType = OperationType.Magenta;
            this.colorActionType = ColorActionType.Invert;
            this.image = image;
            this.scope = scope;
        }

        @Override
        protected Color operateColor(Color color) {
            return new Color(255 - color.getRed(), color.getGreen(), 255 - color.getBlue(), color.getAlpha());
        }
    }

    public static class SetRGB extends PixelsOperation {

        public SetRGB(BufferedImage image, ImageScope scope) {
            this.operationType = OperationType.RGB;
            this.colorActionType = ColorActionType.Set;
            this.image = image;
            this.scope = scope;
        }

        @Override
        protected Color operateColor(Color color) {
            int v = Math.min(Math.max(intPara1, 0), 255);
            return new Color(v, v, v, color.getAlpha());
        }
    }

    public static class IncreaseRGB extends PixelsOperation {

        public IncreaseRGB(BufferedImage image, ImageScope scope) {
            this.operationType = OperationType.RGB;
            this.colorActionType = ColorActionType.Increase;
            this.image = image;
            this.scope = scope;
        }

        @Override
        protected Color operateColor(Color color) {
            return new Color(Math.min(Math.max(color.getRed() + intPara1, 0), 255),
                    Math.min(Math.max(color.getGreen() + intPara1, 0), 255),
                    Math.min(Math.max(color.getBlue() + intPara1, 0), 255), color.getAlpha());
        }
    }

    public static class DecreaseRGB extends PixelsOperation {

        public DecreaseRGB(BufferedImage image, ImageScope scope) {
            this.operationType = OperationType.RGB;
            this.colorActionType = ColorActionType.Decrease;
            this.image = image;
            this.scope = scope;
        }

        @Override
        protected Color operateColor(Color color) {
            return new Color(Math.min(Math.max(color.getRed() - intPara1, 0), 255),
                    Math.min(Math.max(color.getGreen() - intPara1, 0), 255),
                    Math.min(Math.max(color.getBlue() - intPara1, 0), 255), color.getAlpha());
        }
    }

    public static class InvertRGB extends PixelsOperation {

        public InvertRGB(BufferedImage image, ImageScope scope) {
            this.operationType = OperationType.RGB;
            this.colorActionType = ColorActionType.Invert;
            this.image = image;
            this.scope = scope;
        }

        @Override
        protected Color operateColor(Color color) {
            return new Color(255 - color.getRed(), 255 - color.getGreen(), 255 - color.getBlue(), color.getAlpha());
        }
    }

    /*
        get/set
     */
    public OperationType getOperationType() {
        return operationType;
    }

    public PixelsOperation setOperationType(OperationType operationType) {
        this.operationType = operationType;
        return this;
    }

    public BufferedImage getImage() {
        return image;
    }

    public PixelsOperation setImage(BufferedImage image) {
        this.image = image;
        return this;
    }

    public PixelsOperation setImage(Image image) {
        this.image = SwingFXUtils.fromFXImage(image, null);
        return this;
    }

    public boolean isIsDithering() {
        return isDithering;
    }

    public PixelsOperation setIsDithering(boolean isDithering) {
        this.isDithering = isDithering;
        return this;
    }

    public ImageScope getScope() {
        return scope;
    }

    public PixelsOperation setScope(ImageScope scope) {
        this.scope = scope;
        return this;
    }

    public boolean isBoolPara() {
        return boolPara;
    }

    public void setBoolPara(boolean boolPara) {
        this.boolPara = boolPara;
    }

    public int getIntPara1() {
        return intPara1;
    }

    public void setIntPara1(int intPara1) {
        this.intPara1 = intPara1;
    }

    public int getIntPara2() {
        return intPara2;
    }

    public void setIntPara2(int intPara2) {
        this.intPara2 = intPara2;
    }

    public int getIntPara3() {
        return intPara3;
    }

    public void setIntPara3(int intPara3) {
        this.intPara3 = intPara3;
    }

    public float getFloatPara1() {
        return floatPara1;
    }

    public void setFloatPara1(float floatPara1) {
        this.floatPara1 = floatPara1;
    }

    public float getFloatPara2() {
        return floatPara2;
    }

    public void setFloatPara2(float floatPara2) {
        this.floatPara2 = floatPara2;
    }

    public Color[] getThisLine() {
        return thisLine;
    }

    public void setThisLine(Color[] thisLine) {
        this.thisLine = thisLine;
    }

    public Color[] getNextLine() {
        return nextLine;
    }

    public void setNextLine(Color[] nextLine) {
        this.nextLine = nextLine;
    }

    public int getThisLineY() {
        return thisLineY;
    }

    public void setThisLineY(int thisLineY) {
        this.thisLineY = thisLineY;
    }

    public int getImageWidth() {
        return imageWidth;
    }

    public void setImageWidth(int imageWidth) {
        this.imageWidth = imageWidth;
    }

    public int getImageHeight() {
        return imageHeight;
    }

    public void setImageHeight(int imageHeight) {
        this.imageHeight = imageHeight;
    }

    public Color getColorPara1() {
        return colorPara1;
    }

    public void setColorPara1(Color colorPara1) {
        this.colorPara1 = colorPara1;
    }

    public Color getColorPara2() {
        return colorPara2;
    }

    public void setColorPara2(Color colorPara2) {
        this.colorPara2 = colorPara2;
    }

    public ColorActionType getColorActionType() {
        return colorActionType;
    }

    public void setColorActionType(ColorActionType colorActionType) {
        this.colorActionType = colorActionType;
    }

}