Closes #8 and closes #9

This commit is contained in:
ngb 2022-01-06 16:56:11 +01:00
parent 9d78686470
commit bfc3c5b157
8 changed files with 206 additions and 40 deletions

View File

@ -11,6 +11,6 @@ Mit der Zechenmaschine können Schüler:innen ihre Programme von Anfang an mit
Visualisierungen und Interaktionen ausstatten, ohne sich mit der GUI Programmierung
mit Java Swing auseinandersetzen zu müssen.
Die Bibliothek wurde sich stark on [Processing](https://processing.org),
Die Bibliothek wurde stark on [Processing](https://processing.org),
[Engine Alpha](https://engine-alpha.org) und [TigerJython](https://www.tigerjython.ch/de)
inspiriert.

View File

@ -9,6 +9,12 @@ public class ColorLayer extends Layer {
clear();
}
public ColorLayer( int width, int height, Color color ) {
super(width, height);
this.background = color;
clear();
}
@Override
public void setSize( int width, int height ) {
super.setSize(width, height);

View File

@ -8,8 +8,8 @@ public class Constants {
public static final String APP_NAME = "Zeichenmaschine";
public static final int APP_VERSION_MAJ = 0;
public static final int APP_VERSION_MIN = 1;
public static final int APP_VERSION_REV = 5;
public static final int APP_VERSION_MIN = 0;
public static final int APP_VERSION_REV = 8;
public static final String APP_VERSION = APP_VERSION_MAJ + "." + APP_VERSION_MIN + "." + APP_VERSION_REV;

View File

@ -16,11 +16,6 @@ public class ImageLayer extends Layer {
protected boolean redraw = true;
public ImageLayer() {
}
public ImageLayer( String source ) {
image = ImageLoader.loadImage(source);
}

View File

@ -30,9 +30,9 @@ public class Zeichenleinwand extends Canvas {
// Liste der Ebenen initialisieren und die Standardebenen einfügen
layers = new LinkedList<>();
synchronized( layers ) {
layers.add(new ColorLayer(Constants.STD_BACKGROUND));
layers.add(new DrawingLayer());
layers.add(new ShapesLayer());
layers.add(new ColorLayer(width, height, Constants.STD_BACKGROUND));
layers.add(new DrawingLayer(width, height));
layers.add(new ShapesLayer(width, height));
}
}
@ -160,6 +160,16 @@ public class Zeichenleinwand extends Canvas {
render();
}
public void draw( Graphics graphics ) {
Graphics2D g2d = (Graphics2D) graphics.create();
synchronized( layers ) {
for( Layer layer : layers ) {
layer.draw(g2d);
}
}
g2d.dispose();
}
public void render() {
if( getBufferStrategy() == null ) {
allocateBuffer();
@ -192,16 +202,6 @@ public class Zeichenleinwand extends Canvas {
// Repeat the rendering if the drawing buffer was lost
} while( strategy.contentsLost() );
}
/*
Graphics2D g2d = (Graphics2D) g.create();
for( Layer layer : layers ) {
layer.draw(g2d);
}
g2d.dispose();
*/
}
}
}

View File

@ -1,15 +1,21 @@
package schule.ngb.zm;
import schule.ngb.zm.shapes.ShapesLayer;
import schule.ngb.zm.util.ImageLoader;
import javax.swing.*;
import javax.swing.event.MouseInputListener;
import java.awt.*;
import java.awt.Color;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import java.awt.image.RescaleOp;
import java.io.File;
import java.io.IOException;
/**
* Hauptklasse der Zeichenmaschine.
*
* <p>
* Projekte der Zeichenmaschine sollten als Unterklasse implementiert werden.
* Die Klasse übernimmt die Initialisierung eines Programmfensters und der
* nötigen Komponenten.
@ -25,6 +31,7 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke
/*
* Attributes to be accessed by subclasses.
*/
protected Zeichenleinwand canvas;
protected ColorLayer background;
@ -41,18 +48,24 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke
protected double mouseX = 0.0, mouseY = 0.0, pmouseX = 0.0, pmouseY = 0.0;
protected int width = STD_WIDTH, height = STD_HEIGHT;
protected int width, height;
protected int screenWidth, screenHeight;
/*
* Interne Attribute zur Steuerung der Zeichenmaschine.
*/
private Object mouseLock = new Object();
private Object keyboardLock = new Object();
/*
* Interne Attribute zur Steuerung der Zeichenamschine.
*/
//
private JFrame frame;
private GraphicsEnvironment environment;
private GraphicsDevice displayDevice;
private boolean running = false, isDrawing = false, isUpdating = false;
private int framesPerSecond;
@ -61,6 +74,7 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke
private boolean quitAfterTeardown = false, initialized = false;
public Zeichenmaschine() {
this(APP_NAME + " " + APP_VERSION);
}
@ -73,20 +87,37 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch( Exception e ) {
System.err.println("Error setting the look and feel.");
System.err.println("Error setting the look and feel: " + e.getMessage());
}
GraphicsEnvironment environment =
GraphicsEnvironment.getLocalGraphicsEnvironment();
GraphicsDevice displayDevice = environment.getDefaultScreenDevice();
// Looking for the screen currently holding the mouse pointer
// that will be used as the screen device for this Zeichenmaschine
java.awt.Point mouseLoc = MouseInfo.getPointerInfo().getLocation();
environment = GraphicsEnvironment.getLocalGraphicsEnvironment();
GraphicsDevice[] devices = environment.getScreenDevices();
for( GraphicsDevice gd: devices ) {
if( gd.getDefaultConfiguration().getBounds().contains(mouseLoc) ) {
displayDevice = gd;
break;
}
}
if( displayDevice == null ) {
displayDevice = environment.getDefaultScreenDevice();
}
this.width = width;
this.height = height;
java.awt.Rectangle displayBounds = displayDevice.getDefaultConfiguration().getBounds();
this.screenWidth = (int)displayBounds.getWidth();
this.screenHeight = (int)displayBounds.getHeight();
frame = new JFrame(displayDevice.getDefaultConfiguration());
frame.setTitle(title);
frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
this.width = width;
this.height = height;
canvas = new Zeichenleinwand(width, height);
frame.add(canvas);
@ -96,6 +127,7 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke
drawing = getDrawingLayer();
shapes = getShapesLayer();
// TODO: When to call settings?
settings();
canvas.addMouseListener(this);
@ -114,9 +146,7 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke
frame.pack();
frame.setResizable(false);
// TODO: Center on current display (not main display by default)
// TODO: Position at current BlueJ windows if IN_BLUEJ
frame.setLocationRelativeTo(null);
centerFrame();
frame.setVisible(true);
canvas.allocateBuffer();
@ -148,6 +178,19 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke
}
}
public final void centerFrame() {
// TODO: Center on current display (not main display by default)
// TODO: Position at current BlueJ windows if IN_BLUEJ
//frame.setLocationRelativeTo(null);
//frame.setLocationRelativeTo(displayDevice.getFullScreenWindow());
java.awt.Rectangle bounds = displayDevice.getDefaultConfiguration().getBounds();
frame.setLocation(
(int)(bounds.x + (screenWidth-frame.getWidth())/2.0),
(int)(bounds.y + (screenHeight-frame.getHeight())/2.0)
);
}
public final void redraw() {
canvas.render();
//canvas.invalidate();
@ -242,6 +285,64 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke
framesPerSecond = pFramesPerSecond;
}
public void saveImage() {
JFileChooser jfc = new JFileChooser();
jfc.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);
jfc.setMultiSelectionEnabled(false);
int status = jfc.showSaveDialog(frame);
if( status == JFileChooser.APPROVE_OPTION ) {
File outfile = jfc.getSelectedFile();
if( outfile.isDirectory() ) {
outfile = new File(outfile.getAbsolutePath() + File.separator + "zeichenmaschine.png");
}
saveImage(outfile.getAbsolutePath());
}
}
public void saveImage( String filepath ) {
BufferedImage img = new BufferedImage(canvas.getWidth(),canvas.getHeight(), BufferedImage.TYPE_INT_RGB);
Graphics2D g = img.createGraphics();
g.setColor(STD_BACKGROUND.getColor());
g.fillRect(0, 0, img.getWidth(), img.getHeight());
canvas.draw(g);
g.dispose();
try {
ImageLoader.saveImage(img, new File(filepath), true);
} catch ( IOException ex ) {
ex.printStackTrace();
}
}
public ImageLayer snapshot() {
BufferedImage img = new BufferedImage(canvas.getWidth(),canvas.getHeight(), BufferedImage.TYPE_INT_RGB);
Graphics2D g = img.createGraphics();
g.setColor(STD_BACKGROUND.getColor());
g.fillRect(0, 0, img.getWidth(), img.getHeight());
canvas.draw(g);
g.dispose();
/*
float factor = 0.8f;
float base = 255f * (1f - factor);
RescaleOp op = new RescaleOp(factor, base, null);
BufferedImage filteredImage
= new BufferedImage(img.getWidth(), img.getHeight(), img.getType());
op.filter(img, filteredImage);
*/
ImageLayer imgLayer = new ImageLayer(img);
if( canvas.getLayer(0) instanceof ColorLayer ) {
canvas.addLayer(1, imgLayer);
} else {
canvas.addLayer(0, imgLayer);
}
return imgLayer;
}
/*
* Methoden, die von Unterklassen überschrieben werden können / sollen.
*/
@ -366,6 +467,7 @@ public class Zeichenmaschine extends Constants implements MouseInputListener, Ke
pmouseX = mouseX;
pmouseY = mouseY;
// TODO: Seems not right ...
java.awt.Point mouseLoc = MouseInfo.getPointerInfo().getLocation();
java.awt.Point compLoc = canvas.getLocationOnScreen();
mouseX = mouseLoc.x - compLoc.x;

View File

@ -121,7 +121,7 @@ public abstract class Shape extends FilledShape {
* Bestimmt den Ankerpunkt der Form relativ zur oberen linken Ecke und
* abhängig vom gesetzten {@link #setAnchor(Options.Direction) Anker}.
*/
public Point2D.Double getAnchorPoint() {
public Point2D.Double getAnchorPoint( Options.Direction anchor ) {
Point2D.Double anchorpoint = new Point2D.Double(0, 0);
double bHalf = getWidth() * .5, hHalf = getHeight() * .5;
@ -203,18 +203,36 @@ public abstract class Shape extends FilledShape {
this.rotation = angle % 360;
}
public void rotate( Point2D center, double angle ) {
rotate(center.getX(), center.getY(), angle);
}
public void rotate( double x, double y, double angle ) {
this.rotation += angle % 360;
// Rotate x/y position
double x1 = this.x-x, y1 = this.y-y;
double rad = Math.toRadians(angle);
double x2 = x1 * Math.cos(rad) - y1 * Math.sin(rad);
double y2 = x1 * Math.sin(rad) + y1 * Math.cos(rad);
this.x = x2 + x;
this.y = y2 + y;
}
/*public void shear( double dx, double dy ) {
verzerrung.shear(dx, dy);
}*/
public AffineTransform getTransform() {
Point2D.Double anchor = getAnchorPoint();
Point2D.Double anchorPoint = getAnchorPoint(this.anchor);
AffineTransform transform = new AffineTransform();
transform.translate(x, y);
transform.rotate(Math.toRadians(rotation));
//transform.scale(scale, scale);
transform.translate(-anchor.x, -anchor.y);
transform.translate(-anchorPoint.x, -anchorPoint.y);
return transform;
}

View File

@ -1,7 +1,11 @@
package schule.ngb.zm.util;
import javax.imageio.ImageIO;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.awt.image.RenderedImage;
import java.io.File;
import java.io.IOException;
import java.net.URL;
@ -138,4 +142,45 @@ public class ImageLoader {
cacheing = false;
}
public static void saveImage( Image image, File file ) throws IOException {
saveImage(image, file, false);
}
public static void saveImage( Image image, File file, boolean overwriteIfExists ) throws IOException {
BufferedImage outimage = new BufferedImage(image.getWidth(null), image.getHeight(null), BufferedImage.TYPE_INT_RGB);
Graphics2D g = outimage.createGraphics();
g.setColor(Color.WHITE);
g.fillRect(0, 0, outimage.getWidth(), outimage.getHeight());
g.drawImage(image, 0, 0, null);
g.dispose();
saveImage(outimage, file, overwriteIfExists);
}
public static void saveImage( BufferedImage image, File file ) throws IOException {
saveImage(image, file, false);
}
public static void saveImage( BufferedImage image, File file, boolean overwriteIfExists ) throws IOException {
if( file.isFile() ) {
if( !overwriteIfExists ) {
throw new IOException("File already exists. Delete target file before saving image.");
} else if( !file.canWrite() ) {
throw new IOException("File already exists and is not writeable. Change permissions before saving again.");
}
}
String filename = file.getName();
String formatName = "png";
if( filename.lastIndexOf('.') >= 0 ) {
formatName = filename.substring(filename.lastIndexOf('.') + 1);
} else {
file = new File(file.getAbsolutePath() + ".png");
}
ImageIO.write(image, formatName, file);
}
}