simple stamping
This commit is contained in:
commit
432160302e
42
.gitignore
vendored
Normal file
42
.gitignore
vendored
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
/data/
|
||||||
|
|
||||||
|
/.idea/
|
||||||
|
|
||||||
|
target/
|
||||||
|
!.mvn/wrapper/maven-wrapper.jar
|
||||||
|
!**/src/main/**/target/
|
||||||
|
!**/src/test/**/target/
|
||||||
|
|
||||||
|
### IntelliJ IDEA ###
|
||||||
|
.idea/modules.xml
|
||||||
|
.idea/jarRepositories.xml
|
||||||
|
.idea/compiler.xml
|
||||||
|
.idea/libraries/
|
||||||
|
*.iws
|
||||||
|
*.iml
|
||||||
|
*.ipr
|
||||||
|
|
||||||
|
### Eclipse ###
|
||||||
|
.apt_generated
|
||||||
|
.classpath
|
||||||
|
.factorypath
|
||||||
|
.project
|
||||||
|
.settings
|
||||||
|
.springBeans
|
||||||
|
.sts4-cache
|
||||||
|
|
||||||
|
### NetBeans ###
|
||||||
|
/nbproject/private/
|
||||||
|
/nbbuild/
|
||||||
|
/dist/
|
||||||
|
/nbdist/
|
||||||
|
/.nb-gradle/
|
||||||
|
build/
|
||||||
|
!**/src/main/**/build/
|
||||||
|
!**/src/test/**/build/
|
||||||
|
|
||||||
|
### VS Code ###
|
||||||
|
.vscode/
|
||||||
|
|
||||||
|
### Mac OS ###
|
||||||
|
.DS_Store
|
||||||
35
pom.xml
Normal file
35
pom.xml
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<groupId>de.ph87.kindermalen</groupId>
|
||||||
|
<artifactId>KinderMalen</artifactId>
|
||||||
|
<version>1.0-SNAPSHOT</version>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<maven.compiler.source>17</maven.compiler.source>
|
||||||
|
<maven.compiler.target>17</maven.compiler.target>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>ch.qos.logback</groupId>
|
||||||
|
<artifactId>logback-classic</artifactId>
|
||||||
|
<version>1.4.5</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
<version>1.18.24</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.mortennobel</groupId>
|
||||||
|
<artifactId>java-image-scaling</artifactId>
|
||||||
|
<version>0.8.6</version>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
</project>
|
||||||
47
src/main/java/de/ph87/kindermalen/ClickListener.java
Normal file
47
src/main/java/de/ph87/kindermalen/ClickListener.java
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
package de.ph87.kindermalen;
|
||||||
|
|
||||||
|
import java.awt.event.MouseEvent;
|
||||||
|
import java.awt.event.MouseListener;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
public class ClickListener implements MouseListener {
|
||||||
|
|
||||||
|
private final Consumer<MouseEvent> onPress;
|
||||||
|
|
||||||
|
private final Consumer<MouseEvent> onRelease;
|
||||||
|
|
||||||
|
public ClickListener(final Consumer<MouseEvent> onPress, final Consumer<MouseEvent> onRelease) {
|
||||||
|
this.onPress = onPress;
|
||||||
|
this.onRelease = onRelease;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void mouseClicked(final MouseEvent e) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void mousePressed(final MouseEvent e) {
|
||||||
|
if (onPress != null) {
|
||||||
|
onPress.accept(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void mouseReleased(final MouseEvent e) {
|
||||||
|
if (onRelease != null) {
|
||||||
|
onRelease.accept(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void mouseEntered(final MouseEvent e) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void mouseExited(final MouseEvent e) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
33
src/main/java/de/ph87/kindermalen/ImageResizer.java
Normal file
33
src/main/java/de/ph87/kindermalen/ImageResizer.java
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
package de.ph87.kindermalen;
|
||||||
|
|
||||||
|
import com.mortennobel.imagescaling.ResampleFilters;
|
||||||
|
import com.mortennobel.imagescaling.ResampleOp;
|
||||||
|
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
|
||||||
|
public class ImageResizer {
|
||||||
|
|
||||||
|
public static BufferedImage RESIZE(final BufferedImage image, final int maxWidth, final int maxHeight) {
|
||||||
|
final Point size = fitInsideBox(image, maxWidth, maxHeight);
|
||||||
|
final ResampleOp operation = new ResampleOp(size.x, size.y);
|
||||||
|
operation.setFilter(ResampleFilters.getLanczos3Filter());
|
||||||
|
return operation.filter(image, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Point fitInsideBox(final BufferedImage image, final int width, final int height) {
|
||||||
|
int w = image.getWidth();
|
||||||
|
int h = image.getHeight();
|
||||||
|
final double r = (double) w / h;
|
||||||
|
if (w > width) {
|
||||||
|
w = width;
|
||||||
|
h = (int) Math.round(w / r);
|
||||||
|
}
|
||||||
|
if (h > width) {
|
||||||
|
h = height;
|
||||||
|
w = (int) Math.round(h * r);
|
||||||
|
}
|
||||||
|
return new Point(w, h);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
40
src/main/java/de/ph87/kindermalen/KeyListener.java
Normal file
40
src/main/java/de/ph87/kindermalen/KeyListener.java
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
package de.ph87.kindermalen;
|
||||||
|
|
||||||
|
import java.awt.event.KeyEvent;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
public class KeyListener implements java.awt.event.KeyListener {
|
||||||
|
|
||||||
|
private final boolean ctrl;
|
||||||
|
|
||||||
|
private final boolean shift;
|
||||||
|
|
||||||
|
private final int keycode;
|
||||||
|
|
||||||
|
private final Consumer<KeyEvent> next;
|
||||||
|
|
||||||
|
public KeyListener(final boolean ctrl, final boolean shift, final int keycode, final Consumer<KeyEvent> next) {
|
||||||
|
this.ctrl = ctrl;
|
||||||
|
this.shift = shift;
|
||||||
|
this.keycode = keycode;
|
||||||
|
this.next = next;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void keyTyped(final KeyEvent e) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void keyPressed(final KeyEvent e) {
|
||||||
|
if (e.isControlDown() == ctrl && e.isShiftDown() == shift && e.getKeyCode() == keycode) {
|
||||||
|
next.accept(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void keyReleased(final KeyEvent e) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
117
src/main/java/de/ph87/kindermalen/Main.java
Normal file
117
src/main/java/de/ph87/kindermalen/Main.java
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
package de.ph87.kindermalen;
|
||||||
|
|
||||||
|
import de.ph87.kindermalen.drawing.Drawing;
|
||||||
|
import de.ph87.kindermalen.drawing.DrawingPanel;
|
||||||
|
import de.ph87.kindermalen.tool.Tool;
|
||||||
|
import de.ph87.kindermalen.tool.ToolPanel;
|
||||||
|
import de.ph87.kindermalen.tool.stamp.StampTool;
|
||||||
|
import de.ph87.kindermalen.tool.stamp.StampToolPanel;
|
||||||
|
import de.ph87.kindermalen.toolbox.ToolBox;
|
||||||
|
import de.ph87.kindermalen.toolbox.ToolBoxPanel;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.event.KeyEvent;
|
||||||
|
|
||||||
|
public class Main extends JFrame {
|
||||||
|
|
||||||
|
public static final int SCREEN = 1;
|
||||||
|
|
||||||
|
private static final double SIDEBAR_RATIO = 0.15;
|
||||||
|
|
||||||
|
public static final double TOOLBOX_RATIO = 0;
|
||||||
|
|
||||||
|
public static final double PROPERTIES_RATIO = 2;
|
||||||
|
|
||||||
|
private final ToolBox toolBox = new ToolBox();
|
||||||
|
|
||||||
|
private final Drawing drawing = new Drawing(800, 600);
|
||||||
|
|
||||||
|
private final DrawingPanel drawingPanel = new DrawingPanel(toolBox, drawing);
|
||||||
|
|
||||||
|
private final ToolBoxPanel toolBoxPanel = new ToolBoxPanel(toolBox);
|
||||||
|
|
||||||
|
private ToolPanel toolPanel = null;
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
final Main main = new Main();
|
||||||
|
main.setVisible(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Main() {
|
||||||
|
setLayout(new GridBagLayout());
|
||||||
|
final GridBagConstraints c = new GridBagConstraints();
|
||||||
|
|
||||||
|
c.gridx = 0;
|
||||||
|
c.gridy = 0;
|
||||||
|
c.weightx = SIDEBAR_RATIO;
|
||||||
|
c.weighty = TOOLBOX_RATIO;
|
||||||
|
c.fill = GridBagConstraints.BOTH;
|
||||||
|
c.anchor = GridBagConstraints.FIRST_LINE_START;
|
||||||
|
add(toolBoxPanel, c);
|
||||||
|
|
||||||
|
c.gridx = 1;
|
||||||
|
c.gridy = 0;
|
||||||
|
c.weightx = 1 - SIDEBAR_RATIO;
|
||||||
|
c.weighty = 1;
|
||||||
|
c.fill = GridBagConstraints.BOTH;
|
||||||
|
c.gridheight = 3;
|
||||||
|
add(drawingPanel, c);
|
||||||
|
|
||||||
|
pack();
|
||||||
|
|
||||||
|
setFullscreen();
|
||||||
|
setDefaultCloseOperation(EXIT_ON_CLOSE);
|
||||||
|
|
||||||
|
addKeyListener(new KeyListener(true, false, KeyEvent.VK_Z, this::undo));
|
||||||
|
addKeyListener(new KeyListener(true, true, KeyEvent.VK_Z, this::redo));
|
||||||
|
|
||||||
|
toolBox.onToolChange(this::onToolChanged);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void undo(final KeyEvent keyEvent) {
|
||||||
|
drawing.getCurrent().undo();
|
||||||
|
drawingPanel.repaint();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void redo(final KeyEvent keyEvent) {
|
||||||
|
drawing.getCurrent().redo();
|
||||||
|
drawingPanel.repaint();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onToolChanged(final Tool tool) {
|
||||||
|
if (toolPanel != null) {
|
||||||
|
remove(toolPanel);
|
||||||
|
toolPanel = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tool instanceof StampTool) {
|
||||||
|
toolPanel = new StampToolPanel((StampTool) tool);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (toolPanel != null) {
|
||||||
|
final GridBagConstraints c = new GridBagConstraints();
|
||||||
|
c.gridx = 0;
|
||||||
|
c.gridy = 1;
|
||||||
|
c.weightx = SIDEBAR_RATIO;
|
||||||
|
c.weighty = PROPERTIES_RATIO;
|
||||||
|
c.fill = GridBagConstraints.BOTH;
|
||||||
|
c.anchor = GridBagConstraints.FIRST_LINE_START;
|
||||||
|
add(toolPanel, c);
|
||||||
|
pack();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setFullscreen() {
|
||||||
|
final GraphicsEnvironment graphicsEnvironment = GraphicsEnvironment.getLocalGraphicsEnvironment();
|
||||||
|
final GraphicsDevice[] graphicsDevices = graphicsEnvironment.getScreenDevices();
|
||||||
|
if (SCREEN < graphicsDevices.length) {
|
||||||
|
graphicsDevices[SCREEN].setFullScreenWindow(this);
|
||||||
|
} else if (graphicsDevices.length > 0) {
|
||||||
|
graphicsDevices[0].setFullScreenWindow(this);
|
||||||
|
} else {
|
||||||
|
throw new RuntimeException("No Screens Found");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
27
src/main/java/de/ph87/kindermalen/MotionListener.java
Normal file
27
src/main/java/de/ph87/kindermalen/MotionListener.java
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
package de.ph87.kindermalen;
|
||||||
|
|
||||||
|
import java.awt.event.MouseEvent;
|
||||||
|
import java.awt.event.MouseMotionListener;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
public class MotionListener implements MouseMotionListener {
|
||||||
|
|
||||||
|
private final Consumer<MouseEvent> onDrag;
|
||||||
|
|
||||||
|
public MotionListener(final Consumer<MouseEvent> onDrag) {
|
||||||
|
this.onDrag = onDrag;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void mouseDragged(final MouseEvent e) {
|
||||||
|
if (onDrag != null) {
|
||||||
|
onDrag.accept(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void mouseMoved(final MouseEvent e) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
44
src/main/java/de/ph87/kindermalen/Publisher.java
Normal file
44
src/main/java/de/ph87/kindermalen/Publisher.java
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
package de.ph87.kindermalen;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
public class Publisher<T> {
|
||||||
|
|
||||||
|
private final List<Subscription<T>> subscriptions = new ArrayList<>();
|
||||||
|
|
||||||
|
private T last = null;
|
||||||
|
|
||||||
|
private boolean signalling = false;
|
||||||
|
|
||||||
|
public void publish(final T item) {
|
||||||
|
synchronized (subscriptions) {
|
||||||
|
if (signalling) {
|
||||||
|
throw new RuntimeException();
|
||||||
|
}
|
||||||
|
signalling = true;
|
||||||
|
last = item;
|
||||||
|
new ArrayList<>(subscriptions).forEach(o -> o.next(item));
|
||||||
|
signalling = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Subscription<T> subscribe(final Consumer<T> next) {
|
||||||
|
final Subscription<T> subscription = new Subscription<>(next, this::unsubscribe);
|
||||||
|
synchronized (subscriptions) {
|
||||||
|
subscriptions.add(subscription);
|
||||||
|
if (last != null) {
|
||||||
|
subscription.next(last);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return subscription;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void unsubscribe(final Subscription<T> subscription) {
|
||||||
|
synchronized (subscriptions) {
|
||||||
|
subscriptions.remove(subscription);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
24
src/main/java/de/ph87/kindermalen/Subscription.java
Normal file
24
src/main/java/de/ph87/kindermalen/Subscription.java
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
package de.ph87.kindermalen;
|
||||||
|
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
public class Subscription<T> {
|
||||||
|
|
||||||
|
private final Consumer<T> next;
|
||||||
|
|
||||||
|
private final Consumer<Subscription<T>> teardown;
|
||||||
|
|
||||||
|
public Subscription(final Consumer<T> next, final Consumer<Subscription<T>> teardown) {
|
||||||
|
this.next = next;
|
||||||
|
this.teardown = teardown;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void unsubscribe() {
|
||||||
|
teardown.accept(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void next(final T item) {
|
||||||
|
this.next.accept(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
20
src/main/java/de/ph87/kindermalen/drawing/Drawing.java
Normal file
20
src/main/java/de/ph87/kindermalen/drawing/Drawing.java
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
package de.ph87.kindermalen.drawing;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
public class Drawing {
|
||||||
|
|
||||||
|
private final int width;
|
||||||
|
|
||||||
|
private final int height;
|
||||||
|
|
||||||
|
private final Layer current;
|
||||||
|
|
||||||
|
public Drawing(final int width, final int height) {
|
||||||
|
this.width = width;
|
||||||
|
this.height = height;
|
||||||
|
current = new Layer(width, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
64
src/main/java/de/ph87/kindermalen/drawing/DrawingPanel.java
Normal file
64
src/main/java/de/ph87/kindermalen/drawing/DrawingPanel.java
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
package de.ph87.kindermalen.drawing;
|
||||||
|
|
||||||
|
import de.ph87.kindermalen.ClickListener;
|
||||||
|
import de.ph87.kindermalen.MotionListener;
|
||||||
|
import de.ph87.kindermalen.toolbox.ToolBox;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.event.MouseEvent;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public class DrawingPanel extends JPanel {
|
||||||
|
|
||||||
|
private final ToolBox toolBox;
|
||||||
|
|
||||||
|
private final Drawing drawing;
|
||||||
|
|
||||||
|
private Vector lastPoint = null;
|
||||||
|
|
||||||
|
public DrawingPanel(final ToolBox toolBox, final Drawing drawing) {
|
||||||
|
this.toolBox = toolBox;
|
||||||
|
this.drawing = drawing;
|
||||||
|
this.addMouseListener(new ClickListener(this::onPress, null));
|
||||||
|
this.addMouseMotionListener(new MotionListener(this::onDrag));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onPress(final MouseEvent e) {
|
||||||
|
lastPoint = new Vector(e.getPoint());
|
||||||
|
drawing.getCurrent().newRevision();
|
||||||
|
toolBox.getTool().apply(drawing.getCurrent().getCurrent(), e.getX(), e.getY());
|
||||||
|
repaint();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onDrag(final MouseEvent e) {
|
||||||
|
final Vector vector = new Vector(lastPoint, e.getPoint());
|
||||||
|
int i = 0;
|
||||||
|
for (int rest = (int) Math.floor(vector.length); rest >= 20; rest -= 20) {
|
||||||
|
System.out.println(rest);
|
||||||
|
toolBox.getTool().apply(drawing.getCurrent().getCurrent(), e.getX(), e.getY());
|
||||||
|
lastPoint = lastPoint.plus(vector.withLength(20));
|
||||||
|
i++;
|
||||||
|
if (i > 100) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
repaint();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void paint(final Graphics g) {
|
||||||
|
g.setColor(Color.gray);
|
||||||
|
g.fillRect(0, 0, getWidth(), getHeight());
|
||||||
|
|
||||||
|
if (drawing == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
g.setColor(Color.white);
|
||||||
|
g.fillRect(0, 0, drawing.getWidth(), drawing.getHeight());
|
||||||
|
g.drawImage(drawing.getCurrent().getCurrent(), 0, 0, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
72
src/main/java/de/ph87/kindermalen/drawing/Layer.java
Normal file
72
src/main/java/de/ph87/kindermalen/drawing/Layer.java
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
package de.ph87.kindermalen.drawing;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public class Layer {
|
||||||
|
|
||||||
|
private final int width;
|
||||||
|
|
||||||
|
private final int height;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private BufferedImage current;
|
||||||
|
|
||||||
|
private List<BufferedImage> history = new ArrayList<>();
|
||||||
|
|
||||||
|
private int index;
|
||||||
|
|
||||||
|
public Layer(final int width, final int height) {
|
||||||
|
this.width = width;
|
||||||
|
this.height = height;
|
||||||
|
this.current = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
|
||||||
|
this.history.add(current);
|
||||||
|
this.index = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void newRevision() {
|
||||||
|
if (canRedo()) {
|
||||||
|
history = history.subList(0, index + 1);
|
||||||
|
}
|
||||||
|
current = copy();
|
||||||
|
index = history.size();
|
||||||
|
history.add(current);
|
||||||
|
log.info("Revision {} created", index);
|
||||||
|
}
|
||||||
|
|
||||||
|
private BufferedImage copy() {
|
||||||
|
final BufferedImage created = new BufferedImage(current.getWidth(), current.getHeight(), current.getType());
|
||||||
|
current.copyData(created.getRaster());
|
||||||
|
return created;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void undo() {
|
||||||
|
if (index > 0) {
|
||||||
|
index--;
|
||||||
|
current = history.get(index);
|
||||||
|
log.info("UNDO: Revision {} loaded", index);
|
||||||
|
} else {
|
||||||
|
log.warn("No UNDO steps left.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void redo() {
|
||||||
|
if (canRedo()) {
|
||||||
|
index++;
|
||||||
|
current = history.get(index);
|
||||||
|
log.info("REDO: Revision {} loaded", index);
|
||||||
|
} else {
|
||||||
|
log.warn("No REDO steps left.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean canRedo() {
|
||||||
|
return index < history.size() - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
36
src/main/java/de/ph87/kindermalen/drawing/Vector.java
Normal file
36
src/main/java/de/ph87/kindermalen/drawing/Vector.java
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
package de.ph87.kindermalen.drawing;
|
||||||
|
|
||||||
|
import java.awt.*;
|
||||||
|
|
||||||
|
public class Vector {
|
||||||
|
|
||||||
|
public final double x;
|
||||||
|
|
||||||
|
public final double y;
|
||||||
|
|
||||||
|
public final double length;
|
||||||
|
|
||||||
|
public Vector(final Point point) {
|
||||||
|
this(point.x, point.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vector(final Vector start, final Point end) {
|
||||||
|
this(end.x - start.x, end.y - start.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vector(final double x, final double y) {
|
||||||
|
this.x = x;
|
||||||
|
this.y = y;
|
||||||
|
this.length = Math.sqrt(x * x + y * y);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vector withLength(final double newLength) {
|
||||||
|
final double factor = newLength / length;
|
||||||
|
return new Vector(x / factor, y / factor);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vector plus(final Vector other) {
|
||||||
|
return new Vector(x + other.x, y + other.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
18
src/main/java/de/ph87/kindermalen/tool/Tool.java
Normal file
18
src/main/java/de/ph87/kindermalen/tool/Tool.java
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
package de.ph87.kindermalen.tool;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
|
||||||
|
public abstract class Tool {
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private final String name;
|
||||||
|
|
||||||
|
protected Tool(final String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract void apply(final BufferedImage image, final int x, final int y);
|
||||||
|
|
||||||
|
}
|
||||||
18
src/main/java/de/ph87/kindermalen/tool/ToolPanel.java
Normal file
18
src/main/java/de/ph87/kindermalen/tool/ToolPanel.java
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
package de.ph87.kindermalen.tool;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import java.awt.*;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public abstract class ToolPanel<T extends Tool> extends JPanel {
|
||||||
|
|
||||||
|
protected final T tool;
|
||||||
|
|
||||||
|
protected ToolPanel(final T tool) {
|
||||||
|
this.tool = tool;
|
||||||
|
setPreferredSize(new Dimension(100, 100));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
18
src/main/java/de/ph87/kindermalen/tool/stamp/ListHelper.java
Normal file
18
src/main/java/de/ph87/kindermalen/tool/stamp/ListHelper.java
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
package de.ph87.kindermalen.tool.stamp;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
public class ListHelper {
|
||||||
|
|
||||||
|
public static <A, B> List<B> SYNC(final List<A> aList, final List<B> bOld, final Function<B, A> key, final Function<A, B> create, final Function<B, String> order) {
|
||||||
|
final List<B> bAdd = aList.stream().filter(a -> bOld.stream().noneMatch(b -> key.apply(b) == a)).map(create).toList();
|
||||||
|
final List<B> bNew = new ArrayList<>(bOld.stream().filter(panel -> aList.contains(key.apply(panel))).toList());
|
||||||
|
bNew.addAll(bAdd);
|
||||||
|
bNew.sort(Comparator.comparing(order));
|
||||||
|
return bNew;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
43
src/main/java/de/ph87/kindermalen/tool/stamp/Stamp.java
Normal file
43
src/main/java/de/ph87/kindermalen/tool/stamp/Stamp.java
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
package de.ph87.kindermalen.tool.stamp;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.ToString;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import javax.imageio.ImageIO;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import static de.ph87.kindermalen.ImageResizer.RESIZE;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Getter
|
||||||
|
@ToString
|
||||||
|
public class Stamp {
|
||||||
|
|
||||||
|
private final String name;
|
||||||
|
|
||||||
|
private final File file;
|
||||||
|
|
||||||
|
private BufferedImage original = null;
|
||||||
|
|
||||||
|
private final Map<Integer, BufferedImage> sizes = new HashMap<>();
|
||||||
|
|
||||||
|
public Stamp(final File file) {
|
||||||
|
this.name = file.getName().substring(0, file.getName().lastIndexOf("."));
|
||||||
|
this.file = file;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void load() throws IOException {
|
||||||
|
original = ImageIO.read(file);
|
||||||
|
log.info("Stamp loaded: {}", this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public BufferedImage getSize(final int size) {
|
||||||
|
return sizes.computeIfAbsent(size, s -> RESIZE(original, s, s));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
98
src/main/java/de/ph87/kindermalen/tool/stamp/StampTool.java
Normal file
98
src/main/java/de/ph87/kindermalen/tool/stamp/StampTool.java
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
package de.ph87.kindermalen.tool.stamp;
|
||||||
|
|
||||||
|
import de.ph87.kindermalen.tool.Tool;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.ToString;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import static de.ph87.kindermalen.ImageResizer.RESIZE;
|
||||||
|
import static de.ph87.kindermalen.tool.stamp.ListHelper.SYNC;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@ToString
|
||||||
|
public class StampTool extends Tool {
|
||||||
|
|
||||||
|
private static final File STAMPS_DIR = new File("./data/stamps");
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private List<Stamp> stamps = new ArrayList<>();
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private Stamp stamp;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private int size = 100;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private double alpha = 0.75;
|
||||||
|
|
||||||
|
private BufferedImage prepared;
|
||||||
|
|
||||||
|
public StampTool() {
|
||||||
|
super("Stempel");
|
||||||
|
final List<File> files = scan(STAMPS_DIR);
|
||||||
|
stamps = SYNC(files, stamps, Stamp::getFile, Stamp::new, Stamp::getName);
|
||||||
|
stamps.stream().filter(stamp -> stamp.getOriginal() == null).forEach(this::load);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<File> scan(final File dir) {
|
||||||
|
final List<File> files = new ArrayList<>();
|
||||||
|
for (final File child : Objects.requireNonNull(dir.listFiles())) {
|
||||||
|
if (child.isFile()) {
|
||||||
|
files.add(child.getAbsoluteFile());
|
||||||
|
} else if (child.isDirectory()) {
|
||||||
|
files.addAll(scan(child));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return files;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void load(final Stamp stamp) {
|
||||||
|
try {
|
||||||
|
stamp.load();
|
||||||
|
if (this.stamp == stamp) {
|
||||||
|
prepare();
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.error(e.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStamp(final Stamp stamp) {
|
||||||
|
this.stamp = stamp;
|
||||||
|
log.info("Stamp chosen: {}", stamp.getName());
|
||||||
|
prepare();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSize(final int size) {
|
||||||
|
this.size = size;
|
||||||
|
prepare();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAlpha(final double alpha) {
|
||||||
|
this.alpha = alpha;
|
||||||
|
prepare();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void prepare() {
|
||||||
|
if (this.stamp == null || stamp.getOriginal() == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.prepared = RESIZE(stamp.getOriginal(), size, size);
|
||||||
|
log.info("Stamp prepared: {}", stamp.getName());
|
||||||
|
// TODO apply alpha
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void apply(final BufferedImage destination, final int x, final int y) {
|
||||||
|
destination.getGraphics().drawImage(prepared, x - prepared.getWidth() / 2, y - prepared.getHeight() / 2, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,67 @@
|
|||||||
|
package de.ph87.kindermalen.tool.stamp;
|
||||||
|
|
||||||
|
import de.ph87.kindermalen.ClickListener;
|
||||||
|
import de.ph87.kindermalen.tool.ToolPanel;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public class StampToolPanel extends ToolPanel<StampTool> {
|
||||||
|
|
||||||
|
private static final BasicStroke STROKE_BORDER = new BasicStroke(1f);
|
||||||
|
|
||||||
|
private static final int STROKE_HIGHLIGHT_WIDTH = 4;
|
||||||
|
|
||||||
|
private static final BasicStroke STROKE_HIGHLIGHT = new BasicStroke(STROKE_HIGHLIGHT_WIDTH);
|
||||||
|
|
||||||
|
private static final int SIZE = 50;
|
||||||
|
|
||||||
|
public StampToolPanel(final StampTool tool) {
|
||||||
|
super(tool);
|
||||||
|
setLayout(new FlowLayout(FlowLayout.LEADING, 0, 0));
|
||||||
|
tool.getStamps().forEach(stamp -> add(new StampPanel(stamp)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private class StampPanel extends JPanel {
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private final Stamp stamp;
|
||||||
|
|
||||||
|
private final BufferedImage icon;
|
||||||
|
|
||||||
|
private final Point position;
|
||||||
|
|
||||||
|
public StampPanel(final Stamp stamp) {
|
||||||
|
this.stamp = stamp;
|
||||||
|
setPreferredSize(new Dimension(SIZE, SIZE));
|
||||||
|
addMouseListener(new ClickListener(null, e -> tool.setStamp(stamp)));
|
||||||
|
icon = stamp.getSize(SIZE - STROKE_HIGHLIGHT_WIDTH * 2);
|
||||||
|
position = new Point((SIZE - icon.getWidth()) / 2, (SIZE - icon.getHeight()) / 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void paint(final Graphics g) {
|
||||||
|
super.paint(g);
|
||||||
|
|
||||||
|
final Graphics2D g2 = (Graphics2D) g;
|
||||||
|
|
||||||
|
if (tool.getStamp() == stamp) {
|
||||||
|
g2.setColor(Color.orange);
|
||||||
|
g2.setStroke(STROKE_HIGHLIGHT);
|
||||||
|
g2.fillRect(0, 0, getWidth(), getHeight());
|
||||||
|
} else {
|
||||||
|
g2.setColor(Color.white);
|
||||||
|
g2.setStroke(STROKE_BORDER);
|
||||||
|
}
|
||||||
|
|
||||||
|
g2.drawRect(0, 0, SIZE - 1, SIZE - 1);
|
||||||
|
g2.drawImage(icon, position.x, position.y, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
7
src/main/java/de/ph87/kindermalen/toolbox/Observer.java
Normal file
7
src/main/java/de/ph87/kindermalen/toolbox/Observer.java
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
package de.ph87.kindermalen.toolbox;
|
||||||
|
|
||||||
|
public interface Observer<T> {
|
||||||
|
|
||||||
|
void next(final T item);
|
||||||
|
|
||||||
|
}
|
||||||
38
src/main/java/de/ph87/kindermalen/toolbox/ToolBox.java
Normal file
38
src/main/java/de/ph87/kindermalen/toolbox/ToolBox.java
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
package de.ph87.kindermalen.toolbox;
|
||||||
|
|
||||||
|
import de.ph87.kindermalen.Publisher;
|
||||||
|
import de.ph87.kindermalen.Subscription;
|
||||||
|
import de.ph87.kindermalen.tool.Tool;
|
||||||
|
import de.ph87.kindermalen.tool.stamp.StampTool;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public class ToolBox {
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private final List<Tool> tools = List.of(new StampTool());
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private Tool tool;
|
||||||
|
|
||||||
|
private final Publisher<Tool> onToolChange = new Publisher<>();
|
||||||
|
|
||||||
|
public ToolBox() {
|
||||||
|
setTool(tools.get(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTool(final Tool tool) {
|
||||||
|
this.tool = tool;
|
||||||
|
log.info("Tool selected: {}", tool.getName());
|
||||||
|
onToolChange.publish(tool);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Subscription<Tool> onToolChange(final Consumer<Tool> next) {
|
||||||
|
return onToolChange.subscribe(next);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
56
src/main/java/de/ph87/kindermalen/toolbox/ToolBoxPanel.java
Normal file
56
src/main/java/de/ph87/kindermalen/toolbox/ToolBoxPanel.java
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
package de.ph87.kindermalen.toolbox;
|
||||||
|
|
||||||
|
import de.ph87.kindermalen.ClickListener;
|
||||||
|
import de.ph87.kindermalen.tool.Tool;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import java.awt.*;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public class ToolBoxPanel extends JPanel {
|
||||||
|
|
||||||
|
private final ToolBox toolbox;
|
||||||
|
|
||||||
|
public ToolBoxPanel(final ToolBox toolbox) {
|
||||||
|
this.toolbox = toolbox;
|
||||||
|
setLayout(new FlowLayout(FlowLayout.LEADING, 0, 0));
|
||||||
|
for (final Tool tool : toolbox.getTools()) {
|
||||||
|
add(new ToolButton(tool));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void paint(final Graphics g) {
|
||||||
|
super.paint(g);
|
||||||
|
// final Graphics2D g2 = (Graphics2D) g;
|
||||||
|
// g2.setColor(Color.red);
|
||||||
|
// g2.setStroke(new BasicStroke(1f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND, 0, new float[]{2f}, 0));
|
||||||
|
// g2.drawRect(0, 0, getWidth() - 1, getHeight() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ToolButton extends Component {
|
||||||
|
|
||||||
|
private final Tool tool;
|
||||||
|
|
||||||
|
public ToolButton(final Tool tool) {
|
||||||
|
this.tool = tool;
|
||||||
|
setPreferredSize(new Dimension(50, 50));
|
||||||
|
addMouseListener(new ClickListener(null, e -> toolbox.setTool(tool)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void paint(final Graphics g) {
|
||||||
|
g.setColor(Color.magenta);
|
||||||
|
g.fillRect(0, 0, getWidth(), getHeight());
|
||||||
|
|
||||||
|
g.setColor(Color.black);
|
||||||
|
g.setFont(g.getFont().deriveFont(12.0f));
|
||||||
|
g.drawString(tool.getName(), 0, g.getFontMetrics().getHeight());
|
||||||
|
|
||||||
|
g.drawRect(0, 0, getWidth() - 1, getHeight() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
13
src/main/resources/logback.xml
Normal file
13
src/main/resources/logback.xml
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<configuration>
|
||||||
|
|
||||||
|
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||||
|
<encoder>
|
||||||
|
<pattern>%d{HH:mm:ss.SSS} [%thread] %highlight(%-5level) %cyan(%logger{15}) -- %msg %n</pattern>
|
||||||
|
</encoder>
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
<root level="debug">
|
||||||
|
<appender-ref ref="STDOUT"/>
|
||||||
|
</root>
|
||||||
|
|
||||||
|
</configuration>
|
||||||
Loading…
Reference in New Issue
Block a user