diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ef20048 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +lab-02/DocksHobos/.idea/ +lab-02/DocksHobos/out/ +lab-02/DocksHobos/DocksHobos.iml +lab-03/Paint/.idea/ +lab-03/Paint/Paint.iml +lab-03/Paint/out/ \ No newline at end of file diff --git a/lab-03/Paint/src/main/kotlin/by/busskov/paint/Main.kt b/lab-03/Paint/src/main/kotlin/by/busskov/paint/Main.kt new file mode 100644 index 0000000..d8d940d --- /dev/null +++ b/lab-03/Paint/src/main/kotlin/by/busskov/paint/Main.kt @@ -0,0 +1,433 @@ +package by.busskov.paint + +import by.busskov.paint.userActions.* +import javafx.application.Application +import javafx.embed.swing.SwingFXUtils +import javafx.scene.Scene +import javafx.scene.canvas.Canvas +import javafx.scene.canvas.GraphicsContext +import javafx.scene.control.Button +import javafx.scene.control.ColorPicker +import javafx.scene.control.Label +import javafx.scene.control.Menu +import javafx.scene.control.MenuBar +import javafx.scene.control.MenuItem +import javafx.scene.control.Slider +import javafx.scene.control.ToolBar +import javafx.scene.paint.Color +import javafx.stage.Stage +import javafx.scene.image.Image +import javafx.scene.image.ImageView +import javafx.scene.image.WritableImage +import javafx.scene.layout.GridPane +import javafx.scene.shape.StrokeLineCap +import javafx.stage.FileChooser +import java.awt.image.RenderedImage +import java.io.* +import java.util.LinkedList +import javax.imageio.ImageIO +import kotlin.math.min +import kotlin.math.abs + +class PaintApp : Application(), Serializable { + @Transient private val canvas1: Canvas = Canvas(800.0, 600.0) + @Transient private val graphicsContext1: GraphicsContext = canvas1.graphicsContext2D + @Transient private val canvas2: Canvas = Canvas(800.0, 600.0) + @Transient private val graphicsContext2: GraphicsContext = canvas2.graphicsContext2D + @Transient private val toolBar: ToolBar = ToolBar() + @Transient private val menuBar: MenuBar = MenuBar() + @Transient private var paintType: PaintType = PaintType.CURVED_LINE + @Transient private var fillColor: Color = Color.BLACK + @Transient val colorPicker = ColorPicker(Color.BLACK) + @Transient val sizeSlider = Slider(1.0, 100.0, 2.0) + @Transient val sizeLabel = Label("2") + private var userActions: LinkedList = LinkedList() + + override fun start(primaryStage: Stage) { + configureMenuBar() + configureToolBar() + graphicsContext1.fill = Color.WHITE + graphicsContext1.fillRect(0.0, 0.0, canvas1.width, canvas1.height) + graphicsContext1.fill = Color.BLACK + graphicsContext1.lineCap = StrokeLineCap.ROUND + graphicsContext1.lineWidth = 2.0 + graphicsContext2.lineWidth = 2.0 + + val gridPane = GridPane() + gridPane.add(menuBar, 0, 0) + gridPane.add(toolBar, 0, 1) + gridPane.add(canvas1, 0, 2) + gridPane.add(canvas2, 0, 2) + + val scene = Scene(gridPane) + primaryStage.title = "Paint" + primaryStage.scene = scene + primaryStage.show() + + var baseX = 0.0 + var baseY = 0.0 + canvas2.setOnMousePressed { event -> + baseX = round(event.x) + baseY = round(event.y) + } + + canvas2.setOnMouseClicked { event -> + if (paintType == PaintType.FILLING) { + floodFill(event.x.toInt(), event.y.toInt()) + userActions.add(FloodFill(event.x.toInt(), event.y.toInt())) + } + } + + canvas2.setOnMouseDragged { event -> + when(paintType) { + PaintType.CURVED_LINE -> { + graphicsContext1.strokeLine(baseX, baseY, round(event.x), round(event.y)) + userActions.add(RoundLine(baseX, baseY, round(event.x), round(event.y))) + baseX = round(event.x) + baseY = round(event.y) + } + PaintType.ERASE -> { + graphicsContext1.stroke = Color.WHITE + userActions.add(ColorChange(Color.WHITE.red, Color.WHITE.green, Color.WHITE.blue)) + graphicsContext1.strokeLine(baseX, baseY, round(event.x), round(event.y)) + userActions.add(RoundLine(baseX, baseY, round(event.x), round(event.y))) + graphicsContext1.stroke = fillColor + userActions.add(ColorChange(fillColor.red, fillColor.green, fillColor.blue)) + baseX = round(event.x) + baseY = round(event.y) + } + PaintType.OVAL -> { + graphicsContext2.clearRect(0.0, 0.0, canvas2.width, canvas2.height) + graphicsContext2.strokeOval( + round(min(baseX, event.x)), + round(min(baseY, event.y)), + round(abs(event.x - baseX)), + round(abs(event.y - baseY))) + } + PaintType.RECTANGLE -> { + graphicsContext2.clearRect(0.0, 0.0, canvas2.width, canvas2.height) + graphicsContext2.strokeRect( + round(min(baseX, event.x)), + round(min(baseY, event.y)), + round(abs(event.x - baseX)), + round(abs(event.y - baseY))) + } + PaintType.STRAIGHT_LINE -> { + graphicsContext2.clearRect(0.0, 0.0, canvas2.width, canvas2.height) + graphicsContext2.strokeLine( + round(baseX), + round(baseY), + round(event.x), + round(event.y)) + } + else -> {} + } + } + + canvas2.setOnMouseReleased { event -> + when(paintType) { + PaintType.OVAL -> { + graphicsContext2.clearRect(0.0, 0.0, canvas2.width, canvas2.height) + graphicsContext1.strokeOval( + round(min(baseX, event.x)), + round(min(baseY, event.y)), + round(abs(event.x - baseX)), + round(abs(event.y - baseY))) + userActions.add(Oval( + round(min(baseX, event.x)), + round(min(baseY, event.y)), + round(abs(event.x - baseX)), + round(abs(event.y - baseY)) + )) + } + PaintType.RECTANGLE -> { + graphicsContext2.clearRect(0.0, 0.0, canvas2.width, canvas2.height) + graphicsContext1.strokeRect( + round(min(baseX, event.x)), + round(min(baseY, event.y)), + round(abs(event.x - baseX)), + round(abs(event.y - baseY))) + userActions.add(Rectangle( + round(min(baseX, event.x)), + round(min(baseY, event.y)), + round(abs(event.x - baseX)), + round(abs(event.y - baseY)) + )) + } + PaintType.STRAIGHT_LINE -> { + graphicsContext2.clearRect(0.0, 0.0, canvas2.width, canvas2.height) + graphicsContext1.lineCap = StrokeLineCap.SQUARE + graphicsContext1.strokeLine( + round(baseX), + round(baseY), + round(event.x), + round(event.y)) + userActions.add(StraightLine( + round(baseX), + round(baseY), + round(event.x), + round(event.y) + )) + graphicsContext1.lineCap = StrokeLineCap.ROUND + } + else -> {} + } + } + } + + private fun configureMenuBar() { + val fileMenu = Menu("File") + val exportItem = MenuItem("Export PNG") + val saveItem = MenuItem("Save") + val openItem = MenuItem("Open") + fileMenu.items.addAll(exportItem, saveItem, openItem) + exportItem.setOnAction { + export() + } + saveItem.setOnAction { + save(); + } + openItem.setOnAction { + open(); + } + menuBar.menus.add(fileMenu) + } + + private fun configureToolBar() { + val curvedLine = Button() + curvedLine.graphic = ImageView(Image("file:src/main/resources/curved_line.png")) + curvedLine.setOnAction { + paintType = PaintType.CURVED_LINE + } + + val straightLine = Button() + straightLine.graphic = ImageView(Image("file:src/main/resources/straight_line.png")) + straightLine.setOnAction { + paintType = PaintType.STRAIGHT_LINE + } + + val oval = Button() + oval.graphic = ImageView(Image("file:src/main/resources/circle.png")) + oval.setOnAction { + paintType = PaintType.OVAL + } + + val rectangle = Button() + rectangle.graphic = ImageView(Image("file:src/main/resources/rectangle.png")) + rectangle.setOnAction { + paintType = PaintType.RECTANGLE + } + + val filling = Button() + filling.graphic = ImageView(Image("file:src/main/resources/filling.png")) + filling.setOnAction { + paintType = PaintType.FILLING + } + + val eraser = Button() + eraser.graphic = ImageView(Image("file:src/main/resources/eraser.png")) + eraser.setOnAction { + paintType = PaintType.ERASE + } + + colorPicker.setOnAction { + graphicsContext1.stroke = colorPicker.value + graphicsContext1.fill = colorPicker.value + graphicsContext2.stroke = colorPicker.value + graphicsContext2.fill = colorPicker.value + fillColor = colorPicker.value + userActions.add(ColorChange(fillColor.red, fillColor.green, fillColor.blue)) + } + + sizeSlider.setOnMouseDragged { + graphicsContext1.lineWidth = round(sizeSlider.value) + graphicsContext2.lineWidth = graphicsContext1.lineWidth + userActions.add(LineWidthChange(graphicsContext1.lineWidth)) + sizeLabel.text = graphicsContext1.lineWidth.toInt().toString(); + } + sizeSlider.setOnMouseClicked { + graphicsContext1.lineWidth = round(sizeSlider.value) + graphicsContext2.lineWidth = graphicsContext1.lineWidth + userActions.add(LineWidthChange(graphicsContext1.lineWidth)) + sizeLabel.text = graphicsContext1.lineWidth.toInt().toString(); + } + + toolBar.items.addAll( + curvedLine, + straightLine, + oval, + rectangle, + filling, + eraser, + colorPicker, + sizeSlider, + sizeLabel,) + } + + private fun floodFill(x: Int, y: Int) { + val points = Array(canvas1.width.toInt()) {Array(canvas1.height.toInt()){false}} + val pixelReader = canvas1.snapshot(null, null).pixelReader + val color = pixelReader.getColor(x, y) + val pointsQueue = LinkedList>() + pointsQueue.add(x to y) + + while(!pointsQueue.isEmpty()) { + val (curX, curY) = pointsQueue.pollLast() + if (curX < 0 || curX >= points.size || curY < 0 || curY >= points[0].size) { + continue + } + if ( + pixelReader.getColor(curX, curY) == color + && !points[curX][curY]) { + points[curX][curY] = true + pointsQueue.add(curX to (curY - 1)) + pointsQueue.add(curX to (curY + 1)) + pointsQueue.add((curX - 1) to curY) + pointsQueue.add((curX + 1) to curY) + } + } + + val writer = graphicsContext1.pixelWriter + for (i in points.indices) { + for (j in points[0].indices) { + if (points[i][j]) { + writer.setColor(i, j, fillColor) + } + } + } + } + + private fun round(x: Double) : Double { + return x.toInt().toDouble() + } + + private fun export() { + var chooser = FileChooser() + chooser.extensionFilters.addAll( + FileChooser.ExtensionFilter("PNG Files", "*.png") + ) + chooser.title = "Save File" + + val file = chooser.showSaveDialog(Stage()) + if (file != null) { + try { + val writableImage = WritableImage(canvas1.width.toInt(), canvas1.height.toInt()) + canvas1.snapshot(null, writableImage) + val renderedImage: RenderedImage = SwingFXUtils.fromFXImage(writableImage, null) + ImageIO.write(renderedImage, file.extension, file) + } catch (ex: IOException) { + ex.printStackTrace() + println("Error in writing to file!") + } + } + } + + private fun save() { + val chooser = FileChooser() + chooser.extensionFilters.addAll( + FileChooser.ExtensionFilter("Vova Files", "*.vova") + ) + chooser.title = "Save Project" + + val file = chooser.showSaveDialog(Stage()) + if (file != null) { + ObjectOutputStream(FileOutputStream(file)).use {stream -> + stream.writeObject(this) + } + } + } + + private fun open() { + val chooser = FileChooser() + chooser.extensionFilters.addAll( + FileChooser.ExtensionFilter("Vova Files", "*.vova") + ) + chooser.title = "Open Project" + + val file = chooser.showOpenDialog(Stage()) + if (file != null) { + ObjectInputStream(FileInputStream(file)).use { stream -> + val deserialized = stream.readObject() as? PaintApp + deserialized?.let { + graphicsContext1.fill = Color.WHITE + graphicsContext1.fillRect(0.0, 0.0, canvas1.width, canvas1.height) + graphicsContext1.fill = Color.BLACK + + fillColor = Color.BLACK + graphicsContext1.stroke = fillColor + graphicsContext2.stroke = fillColor + graphicsContext2.fill = fillColor + + graphicsContext2.lineWidth = 2.0 + graphicsContext1.lineWidth = 2.0 + + this.userActions = LinkedList(deserialized.userActions) + projectOpen() + } + } + } + } + + private fun projectOpen() { + for (action in userActions) { + when(action) { + is ColorChange -> { + fillColor = Color(action.r, action.g, action.b, 1.0) + colorPicker.value = fillColor + graphicsContext1.stroke = fillColor + graphicsContext1.fill = fillColor + graphicsContext2.stroke = fillColor + graphicsContext2.fill = fillColor + } + is FloodFill -> { + floodFill(action.x, action.y) + } + is Oval -> { + graphicsContext1.strokeOval( + action.x, + action.y, + action.width, + action.height + ) + } + is Rectangle -> { + graphicsContext1.strokeRect( + action.x, + action.y, + action.width, + action.height + ) + } + is RoundLine -> { + graphicsContext1.lineCap = StrokeLineCap.ROUND + graphicsContext1.strokeLine( + action.startX, + action.startY, + action.endX, + action.endY + ) + } + is StraightLine -> { + graphicsContext1.lineCap = StrokeLineCap.SQUARE + graphicsContext1.strokeLine( + action.startX, + action.startY, + action.endX, + action.endY + ) + } + is LineWidthChange -> { + sizeSlider.value = action.width + sizeLabel.text = action.width.toInt().toString() + graphicsContext1.lineWidth = action.width + graphicsContext2.lineWidth = action.width + } + } + graphicsContext1.lineCap = StrokeLineCap.ROUND + } + } +} + +fun main() { + Application.launch(PaintApp::class.java) +} \ No newline at end of file diff --git a/lab-03/Paint/src/main/kotlin/by/busskov/paint/PaintType.kt b/lab-03/Paint/src/main/kotlin/by/busskov/paint/PaintType.kt new file mode 100644 index 0000000..9462f7b --- /dev/null +++ b/lab-03/Paint/src/main/kotlin/by/busskov/paint/PaintType.kt @@ -0,0 +1,10 @@ +package by.busskov.paint + +enum class PaintType { + CURVED_LINE, + OVAL, + RECTANGLE, + FILLING, + STRAIGHT_LINE, + ERASE +} \ No newline at end of file diff --git a/lab-03/Paint/src/main/kotlin/by/busskov/paint/userActions/ColorChange.kt b/lab-03/Paint/src/main/kotlin/by/busskov/paint/userActions/ColorChange.kt new file mode 100644 index 0000000..f6e3dc7 --- /dev/null +++ b/lab-03/Paint/src/main/kotlin/by/busskov/paint/userActions/ColorChange.kt @@ -0,0 +1,3 @@ +package by.busskov.paint.userActions + +class ColorChange(val r: Double, val g: Double, val b: Double) : UserAction() \ No newline at end of file diff --git a/lab-03/Paint/src/main/kotlin/by/busskov/paint/userActions/FloodFill.kt b/lab-03/Paint/src/main/kotlin/by/busskov/paint/userActions/FloodFill.kt new file mode 100644 index 0000000..55c24e4 --- /dev/null +++ b/lab-03/Paint/src/main/kotlin/by/busskov/paint/userActions/FloodFill.kt @@ -0,0 +1,3 @@ +package by.busskov.paint.userActions + +class FloodFill(val x: Int, val y: Int) : UserAction() \ No newline at end of file diff --git a/lab-03/Paint/src/main/kotlin/by/busskov/paint/userActions/Line.kt b/lab-03/Paint/src/main/kotlin/by/busskov/paint/userActions/Line.kt new file mode 100644 index 0000000..0e33207 --- /dev/null +++ b/lab-03/Paint/src/main/kotlin/by/busskov/paint/userActions/Line.kt @@ -0,0 +1,3 @@ +package by.busskov.paint.userActions + +open class Line(val startX: Double, val startY: Double, val endX: Double, val endY: Double) : UserAction() \ No newline at end of file diff --git a/lab-03/Paint/src/main/kotlin/by/busskov/paint/userActions/LineWidthChange.kt b/lab-03/Paint/src/main/kotlin/by/busskov/paint/userActions/LineWidthChange.kt new file mode 100644 index 0000000..a5b3c3d --- /dev/null +++ b/lab-03/Paint/src/main/kotlin/by/busskov/paint/userActions/LineWidthChange.kt @@ -0,0 +1,3 @@ +package by.busskov.paint.userActions + +class LineWidthChange(val width: Double) : UserAction() \ No newline at end of file diff --git a/lab-03/Paint/src/main/kotlin/by/busskov/paint/userActions/Oval.kt b/lab-03/Paint/src/main/kotlin/by/busskov/paint/userActions/Oval.kt new file mode 100644 index 0000000..a2d22b9 --- /dev/null +++ b/lab-03/Paint/src/main/kotlin/by/busskov/paint/userActions/Oval.kt @@ -0,0 +1,3 @@ +package by.busskov.paint.userActions + +class Oval(val x: Double, val y: Double, val width: Double, val height: Double) : UserAction() \ No newline at end of file diff --git a/lab-03/Paint/src/main/kotlin/by/busskov/paint/userActions/Rectangle.kt b/lab-03/Paint/src/main/kotlin/by/busskov/paint/userActions/Rectangle.kt new file mode 100644 index 0000000..e983691 --- /dev/null +++ b/lab-03/Paint/src/main/kotlin/by/busskov/paint/userActions/Rectangle.kt @@ -0,0 +1,3 @@ +package by.busskov.paint.userActions + +class Rectangle(val x: Double, val y: Double, val width: Double, val height: Double) : UserAction() \ No newline at end of file diff --git a/lab-03/Paint/src/main/kotlin/by/busskov/paint/userActions/RoundLine.kt b/lab-03/Paint/src/main/kotlin/by/busskov/paint/userActions/RoundLine.kt new file mode 100644 index 0000000..fe11880 --- /dev/null +++ b/lab-03/Paint/src/main/kotlin/by/busskov/paint/userActions/RoundLine.kt @@ -0,0 +1,3 @@ +package by.busskov.paint.userActions + +class RoundLine(x1: Double, y1: Double, x2: Double, y2: Double) : Line(x1, y1, x2, y2) \ No newline at end of file diff --git a/lab-03/Paint/src/main/kotlin/by/busskov/paint/userActions/StraightLine.kt b/lab-03/Paint/src/main/kotlin/by/busskov/paint/userActions/StraightLine.kt new file mode 100644 index 0000000..a1a0720 --- /dev/null +++ b/lab-03/Paint/src/main/kotlin/by/busskov/paint/userActions/StraightLine.kt @@ -0,0 +1,3 @@ +package by.busskov.paint.userActions + +class StraightLine (x1: Double, y1: Double, x2: Double, y2: Double) : Line(x1, y1, x2, y2) \ No newline at end of file diff --git a/lab-03/Paint/src/main/kotlin/by/busskov/paint/userActions/UserAction.kt b/lab-03/Paint/src/main/kotlin/by/busskov/paint/userActions/UserAction.kt new file mode 100644 index 0000000..75a4dbd --- /dev/null +++ b/lab-03/Paint/src/main/kotlin/by/busskov/paint/userActions/UserAction.kt @@ -0,0 +1,5 @@ +package by.busskov.paint.userActions + +import java.io.Serializable + +open class UserAction : Serializable \ No newline at end of file diff --git a/lab-03/Paint/src/main/resources/circle.png b/lab-03/Paint/src/main/resources/circle.png new file mode 100644 index 0000000..4ddb2f3 Binary files /dev/null and b/lab-03/Paint/src/main/resources/circle.png differ diff --git a/lab-03/Paint/src/main/resources/curved_line.png b/lab-03/Paint/src/main/resources/curved_line.png new file mode 100644 index 0000000..9535d02 Binary files /dev/null and b/lab-03/Paint/src/main/resources/curved_line.png differ diff --git a/lab-03/Paint/src/main/resources/eraser.png b/lab-03/Paint/src/main/resources/eraser.png new file mode 100644 index 0000000..4decc7a Binary files /dev/null and b/lab-03/Paint/src/main/resources/eraser.png differ diff --git a/lab-03/Paint/src/main/resources/filling.png b/lab-03/Paint/src/main/resources/filling.png new file mode 100644 index 0000000..ca6b3a9 Binary files /dev/null and b/lab-03/Paint/src/main/resources/filling.png differ diff --git a/lab-03/Paint/src/main/resources/rectangle.png b/lab-03/Paint/src/main/resources/rectangle.png new file mode 100644 index 0000000..23a765d Binary files /dev/null and b/lab-03/Paint/src/main/resources/rectangle.png differ diff --git a/lab-03/Paint/src/main/resources/straight_line.png b/lab-03/Paint/src/main/resources/straight_line.png new file mode 100644 index 0000000..4889118 Binary files /dev/null and b/lab-03/Paint/src/main/resources/straight_line.png differ diff --git a/lab-03/Project setup.txt b/lab-03/Project setup.txt new file mode 100644 index 0000000..a07a502 --- /dev/null +++ b/lab-03/Project setup.txt @@ -0,0 +1,2 @@ +https://gluonhq.com/products/javafx/ Скачал здесь архив и распаковал +в IDEA File->Project Structure->Libraries добавить папку lib из скачанного архива