package com.cgvsu.rasterizationfxapp.figure;

import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.image.PixelWriter;
import javafx.scene.image.WritableImage;
import javafx.scene.paint.Color;

public class DrawLine {


Canvas canvas;

public void setCanvas(Canvas canvas) {
    this.canvas = canvas;
}

public void drawLine(GraphicsContext graphicsContext, Point point1, Point point2, int operation) {
    if (operation >= 1) {
        drawWuLine(graphicsContext, point1, point2);
    } else if (operation == 0) {
        drawBresenhamLine(graphicsContext, point1, point2);
    } else {
        drawDDALine(graphicsContext, point1, point2);
    }
}


private void drawWuLine(GraphicsContext graphicsContext, Point point1, Point point2) {
    int x0 = point1.getX();
    int y0 = point1.getY();
    int x1 = point2.getX();
    int y1 = point2.getY();

    WritableImage writableImage = new WritableImage((int) canvas.getWidth(), (int) canvas.getHeight());
    PixelWriter pixelWriter = writableImage.getPixelWriter();

    boolean steep = Math.abs(y1 - y0) > Math.abs(x1 - x0);
    if (steep) {
        int temp = x0;
        x0 = y0;
        y0 = temp;
        temp = x1;
        x1 = y1;
        y1 = temp;
    }
    if (x0 > x1) {
        int temp = x0;
        x0 = x1;
        x1 = temp;
        temp = y0;
        y0 = y1;
        y1 = temp;
    }
    double dx = x1 - x0;
    double dy = y1 - y0;
    double gradient = dx == 0 ? 1 : dy / dx;
    // первая точка
    double xEnd = round(x0);
    double yEnd = y0 + gradient * (xEnd - x0);
    double xGap = rfpart(x0 + 0.5);
    int xPixel1 = (int) xEnd;
    int yPixel1 = (int) Math.floor(yEnd);

    if (steep) {
        plot(pixelWriter, yPixel1, xPixel1, rfpart(yEnd) * xGap);
        plot(pixelWriter, yPixel1 + 1, xPixel1, rfpart(yEnd) * xGap);
    } else {
        plot(pixelWriter, xPixel1, yPixel1, rfpart(yEnd) * xGap);
        plot(pixelWriter, xPixel1, yPixel1 + 1, rfpart(yEnd) * xGap);
    }

    double intery = yEnd + gradient;
    // 2 - последняя точка
    xEnd = round(x1);
    yEnd = y1 + gradient * (xEnd - x1);
    xGap = fpart(x1 + 0.5);
    int xPixel2 = (int) xEnd;
    int yPixel2 = (int) Math.floor(yEnd);
    if (steep) {
        plot(pixelWriter, yPixel2, xPixel2, rfpart(yEnd) * xGap);
        plot(pixelWriter, yPixel2 + 1, xPixel2, rfpart(yEnd) * xGap);
    } else {
        plot(pixelWriter, xPixel2, yPixel2, rfpart(yEnd) * xGap);
        plot(pixelWriter, xPixel2, yPixel2 + 1, rfpart(yEnd) * xGap);
    }

    // сама линия
    if (steep) {
        for (int x = xPixel1 + 1; x < xPixel2; x++) {
            plot(pixelWriter, (int) Math.floor(intery), x, rfpart(intery));
            plot(pixelWriter, (int) Math.floor(intery) + 1, x, fpart(intery));
            intery += gradient;
        }
    } else {
        for (int x = xPixel1 + 1; x < xPixel2; x++) {
            plot(pixelWriter, x, (int) Math.floor(intery), rfpart(intery));
            plot(pixelWriter, x, (int) Math.floor(intery) + 1, fpart(intery));
            intery += gradient;
        }
    }
    graphicsContext.drawImage(writableImage, 0, 0);

}

private void plot(PixelWriter pixelWriter, int x, int y, double brightness) {
    if (x >= 0 && y >= 0 && x < canvas.getWidth() && y < canvas.getHeight()) {
        // Ограничиваем яркость в диапазоне от 0 до 1
        brightness = Math.max(0, Math.min(1, brightness));
        Color color = new Color(0, 0, 0, brightness);
        pixelWriter.setColor(x, y, color);
    }
}

private double fpart(double x) {
    return x - Math.floor(x);
}

private double rfpart(double x) {
    return 1 - fpart(x);
}

private double round(double x) {
    return Math.floor(x + 0.5);
}

private void drawBresenhamLine(GraphicsContext graphicsContext, Point point1, Point point2) {
    int x0 = point1.getX();
    int y0 = point1.getY();
    int x1 = point2.getX();
    int y1 = point2.getY();

    int dx = Math.abs(x1 - x0);
    int dy = Math.abs(y1 - y0);

    int sx = x0 < x1 ? 1 : -1;
    int sy = y0 < y1 ? 1 : -1;

    int err = dx - dy;

    WritableImage writableImage = new WritableImage((int) canvas.getWidth(), (int) canvas.getHeight());
    PixelWriter pixelWriter = writableImage.getPixelWriter();

    while (true) {
        pixelWriter.setColor(x0, y0, Color.BLACK);

        if (x0 == x1 && y0 == y1)
            break;

        int e2 = 2 * err;

        if (e2 > -dy) {
            err -= dy;
            x0 += sx;
        }
        if (e2 < dx) {
            err += dx;
            y0 += sy;
        }
    }

    graphicsContext.drawImage(writableImage, 0, 0);
}


private void drawDDALine(GraphicsContext graphicsContext, Point point1, Point point2) {

    double dx = point2.getX() - point1.getX();
    double dy = point2.getY() - point1.getY();
    final double step;
    if (Math.abs(dx) > Math.abs(dy)) {
        step = Math.abs(dx);
    } else {
        step = Math.abs(dy);
    }
    dy /= step;
    dx /= step;
    double x = point1.getX();
    double y = point1.getY();
    WritableImage writableImage = new WritableImage((int) canvas.getWidth(), (int) canvas.getHeight());
    PixelWriter pixelWriter = writableImage.getPixelWriter();
    for (int i = 0; i < step; i++) {


        pixelWriter.setColor((int) x, (int) y, Color.BLACK);

        x = x + dx;
        y = y + dy;
    }
    graphicsContext.drawImage(writableImage, 0, 0);
}
}
package com.cgvsu.rasterizationfxapp.figure;

import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.image.PixelWriter;
import javafx.scene.image.WritableImage;
import javafx.scene.paint.Color;

public class Oval {


private static void fillScanline(
        PixelWriter pixelWriter,
        int x,
        int y,
        Point center,
        Color centerColor,
        Color edgeColor,
        double focusA,
        double focusB) {
    int startX = (int) (center.getX() - x);
    int endX = (int) (center.getX() + x);
    int y1 = (int) (center.getY() + y);
    int y2 = (int) (center.getY() - y);

    for (int xi = startX; xi <= endX; xi++) {
        interpolateAndSetColor(pixelWriter, xi, y1, center, centerColor, edgeColor, focusA, focusB);
        interpolateAndSetColor(pixelWriter, xi, y2, center, centerColor, edgeColor, focusA, focusB);
    }
}

private static void interpolateAndSetColor(
        PixelWriter pixelWriter,
        int x,
        int y,
        Point center,
        Color centerColor,
        Color edgeColor,
        double focusA,
        double focusB) {
    // Вычисляем нормализованное расстояние от центра
    double dx = x - center.getX();
    double dy = y - center.getY();
    double distance = Math.sqrt(dx * dx + dy * dy);
    double maxDistance = Math.sqrt(focusA * focusA + focusB * focusB);

    // t между 0 и 1
    double t = Math.min(distance / maxDistance, 1.0);

    // интерполяция цвета
    double red = (1 - t) * centerColor.getRed() + t * edgeColor.getRed();
    double green = (1 - t) * centerColor.getGreen() + t * edgeColor.getGreen();
    double blue = (1 - t) * centerColor.getBlue() + t * edgeColor.getBlue();
    double opacity = (1 - t) * centerColor.getOpacity() + t * edgeColor.getOpacity();

    // цвета должны быть в диапозоне [0,1]
    red = Math.max(0, Math.min(red, 1));
    green = Math.max(0, Math.min(green, 1));
    blue = Math.max(0, Math.min(blue, 1));
    opacity = Math.max(0, Math.min(opacity, 1));

    Color color = new Color(red, green, blue, opacity);

    pixelWriter.setColor(x, y, color);
}

protected static void drawOvalByInterpolate(
        GraphicsContext graphicsContext,
        int x, int y,
        final int width, final int height,
        final Color centerColor,
        final Color edgeColor, Canvas canvas) {
    WritableImage writableImage = new WritableImage(
            (int) canvas.getWidth(),
            (int) canvas.getHeight());
    PixelWriter pixelWriter = writableImage.getPixelWriter();
    int focusA = width / 2;
    int focusB = height / 2;

    Point center = new Point(x + focusA, y + focusB);

    int xi = 0;
    int yi = focusB;
    double a2 = focusA * focusA;
    double b2 = focusB * focusB;

    // Decision parameters
    double d1 = b2 - a2 * focusB + 0.25 * a2;
    double dx = 2 * b2 * xi;
    double dy = 2 * a2 * yi;

    // Region 1
    while (dx < dy) {
        fillScanline(pixelWriter, xi, yi, center, centerColor, edgeColor, focusA, focusB);
        if (d1 < 0) {
            xi++;
            dx += 2 * b2;
            d1 += dx + b2;
        } else {
            xi++;
            yi--;
            dx += 2 * b2;
            dy -= 2 * a2;
            d1 += dx - dy + b2;
        }
    }

    // Region 2
    double d2 = b2 * (xi + 0.5) * (xi + 0.5) + a2 * (yi - 1) * (yi - 1) - a2 * b2;
    while (yi >= 0) {
        fillScanline(pixelWriter, xi, yi, center, centerColor, edgeColor, focusA, focusB);
        if (d2 > 0) {
            yi--;
            dy -= 2 * a2;
            d2 += a2 - dy;
        } else {
            xi++;
            yi--;
            dx += 2 * b2;
            dy -= 2 * a2;
            d2 += dx - dy + a2;
        }
    }

    graphicsContext.drawImage(writableImage, 0, 0);
}
}
package com.cgvsu.rasterizationfxapp.figure;

import javafx.beans.NamedArg;

public class Point {
private int x;
private int y;


public Point(@NamedArg("x") int x, @NamedArg("y") int y) {
    this.x = x;
    this.y = y;
}

public int getX() {
    return x;
}

public void setX(int x) {
    this.x = x;
}

public int getY() {
    return y;
}

public void setY(int y) {
    this.y = y;
}

public double distance(double x, double y) {
    double distanceForX = this.getX() - x;
    double distanceForY = this.getY() - y;
    return Math.sqrt(distanceForX * distanceForX + distanceForY * distanceForY);
}
}
package com.cgvsu.rasterizationfxapp.figure;

import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.image.PixelWriter;
import javafx.scene.image.WritableImage;
import javafx.scene.paint.Color;
import static com.cgvsu.rasterizationfxapp.figure.Oval.drawOvalByInterpolate;

public class Rasterization {
private static Canvas canvas;


public static void drawRectangle(
        final GraphicsContext graphicsContext,
        final int x, final int y,
        final int width, final int height,
        final Color color) {

    final PixelWriter pixelWriter = graphicsContext.getPixelWriter();
    for (int row = y; row < y + height; ++row) {
        for (int col = x; col < x + width; ++col) {
            pixelWriter.setColor(col, row, color);
        }
    }
}
public static void drawOval(
        GraphicsContext graphicsContext,
        int x, int y,
        final int width, final int height,
        final Color centerColor,
        final Color edgeColor) {
    drawOvalByInterpolate(graphicsContext, x, y, width, height, centerColor, edgeColor, canvas);
}

public static void setCanvas(Canvas canvas) {
    Rasterization.canvas = canvas;
}
}package com.cgvsu.rasterizationfxapp;

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.stage.Stage;

import java.io.IOException;

public class RasterizationApplication extends Application {
@Override
public void start(Stage stage) throws IOException {
FXMLLoader fxmlLoader = new FXMLLoader(RasterizationApplication.class.getResource("mainwindow.fxml"));
Scene scene = new Scene(fxmlLoader.load(), 800, 600);
stage.setTitle("Rasterization App");
stage.setScene(scene);
stage.show();
}


public static void main(String[] args) {
    launch();
}
}package com.cgvsu.rasterizationfxapp;

import com.cgvsu.rasterizationfxapp.figure.DrawLine;
import com.cgvsu.rasterizationfxapp.figure.Point;
import com.cgvsu.rasterizationfxapp.figure.Rasterization;
import javafx.fxml.FXML;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.AnchorPane;
import javafx.scene.paint.Color;

import java.util.ArrayList;
import java.util.Objects;

public class RasterizationController {


@FXML
AnchorPane anchorPane;
ArrayList<Point> points = new ArrayList<Point>();
@FXML
private Canvas canvas;

public Canvas getCanvas() {
    return canvas;
}
@FXML
private void initialize() {
    anchorPane.prefWidthProperty().addListener((ov, oldValue, newValue) -> canvas.setWidth(newValue.doubleValue()));
    anchorPane.prefHeightProperty().addListener((ov, oldValue, newValue) -> canvas.setHeight(newValue.doubleValue()));
    canvas.setOnMouseClicked(event -> {
        if (Objects.requireNonNull(event.getButton()) == MouseButton.PRIMARY) {
            handlePrimaryClick(canvas.getGraphicsContext2D(), event);
        }
    });
    Rasterization.setCanvas(canvas);
    Rasterization.drawOval(canvas.getGraphicsContext2D(), 0, 0, 500, 200, Color.BLUE, Color.RED);
}

private void handlePrimaryClick(GraphicsContext graphicsContext, MouseEvent event) {
    DrawLine draw = new DrawLine();
    draw.setCanvas(canvas);
    final Point clickPoint = new Point((int) event.getX(), (int) event.getY());
    if (!points.isEmpty()) {
        final Point lastPoint = points.get(points.size() - 1);
        draw.drawLine(graphicsContext, lastPoint, clickPoint, 1);
    }
    points.add(clickPoint);
}
} соблюдены ли принципы ооп, сделай юнит тесты