parent
9d78686470
commit
bfc3c5b157
|
@ -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.
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -16,11 +16,6 @@ public class ImageLayer extends Layer {
|
|||
|
||||
protected boolean redraw = true;
|
||||
|
||||
public ImageLayer() {
|
||||
|
||||
}
|
||||
|
||||
|
||||
public ImageLayer( String source ) {
|
||||
image = ImageLoader.loadImage(source);
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
*/
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue