I have a canvas where I can draw bounding boxes on a canvas in Vaadin, and I am trying to implement an undo function whereby the last drawn box will both be removed from the array and on the canvas. In the undoLast method (which is found in Canvas.java and called in the MainLayout.java), I remove the last element of the array (arrayBoxes which contains all the boxes drawn), and attempt to clear the entire canvas before redrawing the ones left in the array. However, I am getting an array error.
MainLayout:
package com.vaadin.starter.beveragebuddy.backend;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.dependency.HtmlImport;
import com.vaadin.flow.component.html.Div;
import com.vaadin.flow.component.html.H2;
import com.vaadin.flow.component.html.Label;
import com.vaadin.flow.component.html.NativeButton;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.theme.Theme;
import com.vaadin.flow.theme.lumo.Lumo;
import com.vaadin.starter.beveragebuddy.ui.components.BoundingBox;
import com.vaadin.starter.beveragebuddy.ui.components.Canvas;
import com.vaadin.starter.beveragebuddy.ui.components.CanvasRenderingContext2D;
import com.vaadin.starter.beveragebuddy.ui.components.MousePosition;
import com.vaadin.flow.component.textfield.TextField;
import java.util.ArrayList;
/**
* The main layout contains the header with the navigation buttons, and the
* child views below that.
*/
#HtmlImport("frontend://styles/shared-styles.html")
#Route("")
#Theme(Lumo.class)
public class MainLayout extends VerticalLayout {
private CanvasRenderingContext2D ctx;
private Canvas canvas;
ArrayList<MousePosition> mousePosArray = Canvas.getMousePosArray();
ArrayList<BoundingBox> bb = Canvas.getArrayBoxes();
public static int count = 0;
public MainLayout() {
VerticalLayout footerLayout = new VerticalLayout();
H2 title = new H2("Annotation UI");
title.addClassName("main-layout__title");
canvas = new Canvas(1580, 700);
ctx = canvas.getContext();
Div buttons = new Div();
buttons.add(new NativeButton("Save Annotations"));
buttons.add(new NativeButton("Previous Picture"));
buttons.add(new NativeButton("Next Picture"));
buttons.add(new NativeButton("Undo", e -> Canvas.undoLast()));
buttons.add(new NativeButton("Clear Canvas",
e -> ctx.clearRect(0, 0, 1580, 700)));
add(canvas, buttons);
Label label = new Label();
canvas.addComponent(label);
add(label);
// HorizontalLayout fieldLayout = new HorizontalLayout();
TextField boxname = new TextField();
boxname.setLabel("Box Name:");
boxname.setPlaceholder("Enter bounding box name");
TextField boxcategory = new TextField();
boxcategory.setLabel("Box Category:");
boxcategory.setPlaceholder("Enter bounding box category");
add(boxname, boxcategory);
Button submitButton = new Button("Submit");
submitButton.addClickListener(event -> {
if (count == 0){
count = count + 1;
}
bb.get(count - 1).setName(boxname.getValue());
bb.get(count - 1).setBoxcategory(boxcategory.getValue());
count = count + 1;
boxname.clear();
boxcategory.clear();
System.out.println(bb.toString());
});
add(submitButton);
canvas.addMouseMoveListener(() -> label.setText("Coordinates: " + mousePosArray.get(0)));
}
}
Canvas.java:
package com.vaadin.starter.beveragebuddy.ui.components;
import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.HasSize;
import com.vaadin.flow.component.HasStyle;
import com.vaadin.flow.component.Tag;
import com.vaadin.flow.component.html.Label;
import com.vaadin.flow.dom.Element;
import com.vaadin.flow.dom.ElementFactory;
import com.vaadin.flow.shared.Registration;
import elemental.json.JsonObject;
import java.util.ArrayList;
import java.util.List;
/**
* Canvas component that you can draw shapes and images on. It's a Java wrapper
* for the
* <a href="https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API">HTML5
* canvas</a>.
* <p>
* Use {#link #getContext()} to get API for rendering shapes and images on the
* canvas.
* <p>
*/
#Tag("canvas")
#SuppressWarnings("serial")
public class Canvas extends Component implements HasStyle, HasSize {
private static CanvasRenderingContext2D context;
private Element element;
private boolean isDrawing = false;
private boolean mouseIsDown = false;
private double endX;
private double endY;
public static ArrayList <BoundingBox> arrayBoxes = new ArrayList<BoundingBox>();
public static ArrayList <MousePosition> mousePosArray = new ArrayList<MousePosition>();
private List<Runnable> mouseMoveListeners = new ArrayList<>(0);
public static ArrayList<BoundingBox> getArrayBoxes() {
return arrayBoxes;
}
public static ArrayList<MousePosition> getMousePosArray() {
return mousePosArray;
}
public static void setMousePosArray(ArrayList<MousePosition> mousePosArray) {
Canvas.mousePosArray = mousePosArray;
}
/**
* Creates a new canvas component with the given size.
* <p>
* Use the API provided by {#link #getContext()} to render graphics on the
* canvas.
* <p>
* The width and height parameters will be used for the canvas' coordinate
* system. They will determine the size of the component in pixels, unless
* you explicitly set the component's size with {#link #setWidth(String)} or
* {#link #setHeight(String)}.
*
// * #param width
// * the width of the canvas
// * #param height
// * the height of the canvas
// */
public Registration addMouseMoveListener(Runnable listener) {
mouseMoveListeners.add(listener);
return () -> mouseMoveListeners.remove(listener);
}
public Canvas(int width, int height) {
context = new CanvasRenderingContext2D(this);
element = getElement();
element.getStyle().set("border", "1px solid");
getElement().setAttribute("width", String.valueOf(width));
getElement().setAttribute("height", String.valueOf(height));
element.addEventListener("mousedown", event -> { // Retrieve Starting Position on MouseDown
Element boundingBoxResult = ElementFactory.createDiv();
element.appendChild(boundingBoxResult);
JsonObject evtData = event.getEventData();
double xBox = evtData.getNumber("event.x");
double yBox = evtData.getNumber("event.y");
boundingBoxResult.setAttribute("data-x", String.format("%f", xBox));
boundingBoxResult.setAttribute("data-y", String.format("%f", yBox));
BoundingBox newBox = new BoundingBox("","", xBox, yBox, 0.0, 0.0);
arrayBoxes.add(newBox);
isDrawing = true;
mouseIsDown=true;
mouseMoveListeners.forEach(Runnable::run);
}).addEventData("event.x").addEventData("event.y");
element.addEventListener("mouseup", event -> { // Draw Box on MouseUp
Element boundingBoxResult2 = ElementFactory.createDiv();
element.appendChild(boundingBoxResult2);
JsonObject evtData2 = event.getEventData();
endX = evtData2.getNumber("event.x");
endY = evtData2.getNumber("event.y");
boundingBoxResult2.setAttribute("end-x", String.format("%f", endX));
boundingBoxResult2.setAttribute("end-y", String.format("%f", endY));
double xcoordi = 0;
double ycoordi = 0;
double boxWidth = 0;
double boxHeight = 0;
for (int i = 0; i < arrayBoxes.size(); i++) {
arrayBoxes.get(i).setWidth(endX, arrayBoxes.get(i).xcoordi);
arrayBoxes.get(i).setHeight(endY, arrayBoxes.get(i).ycoordi);
xcoordi = arrayBoxes.get(i).getXcoordi();
ycoordi = arrayBoxes.get(i).getYcoordi();
boxWidth = arrayBoxes.get(i).getWidth();
boxHeight = arrayBoxes.get(i).getHeight();
}
mouseIsDown=false;
context.beginPath();
context.setStrokeStyle("green");
context.setLineWidth(2);
context.strokeRect(xcoordi, ycoordi, boxWidth, boxHeight);
context.stroke();
context.fill();
System.out.println(arrayBoxes.toString());
mouseMoveListeners.forEach(Runnable::run);
}).addEventData("event.x").addEventData("event.y");
element.addEventListener("mousemove", event -> { // Retrieve Mouse Position when Moving
JsonObject mousePos = event.getEventData();
double mouseX = mousePos.getNumber("event.x");
double mouseY = mousePos.getNumber("event.y");
MousePosition currentPos = new MousePosition(mouseX, mouseY);
mousePosArray.add(0, currentPos);
setMousePosArray(mousePosArray);
mouseMoveListeners.forEach(Runnable::run);
}).addEventData("event.x").addEventData("event.y");
}
public static void undoLast() {
if (arrayBoxes.size() > 0) {
arrayBoxes.remove(arrayBoxes.size() - 1);
}
// System.out.println(arrayBoxes.toString());
System.out.println(arrayBoxes.get(0).toString());
System.out.println(arrayBoxes.size());
for (int i = 0; i < arrayBoxes.size(); i++){
context.clearRect(0, 0, 1580, 700);
context.beginPath();
context.setStrokeStyle("limegreen");
context.setLineWidth(2);
context.strokeRect(arrayBoxes.get(i).xcoordi, arrayBoxes.get(i).ycoordi, arrayBoxes.get(i).boxWidth, arrayBoxes.get(i).boxHeight);
context.fill();
}
}
/**
* Gets the context for rendering shapes and images in the canvas.
* <p>
* It is a Java wrapper for the <a href=
* "https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D">same
* client-side API</a>.
*
* #return the 2D rendering context of this canvas
*/
public CanvasRenderingContext2D getContext() {
return context;
}
/**
* {#inheritDoc}
* <p>
* <b>NOTE:</b> Canvas has an internal coordinate system that it uses for
* drawing, and it uses the width and height provided in the constructor.
* This coordinate system is independent of the component's size. Changing
* the component's size with this method may scale/stretch the rendered
* graphics.
*/
#Override
public void setWidth(String width) {
HasSize.super.setWidth(width);
}
/**
* {#inheritDoc}
* <p>
* <b>NOTE:</b> Canvas has an internal coordinate system that it uses for
* drawing, and it uses the width and height provided in the constructor.
* This coordinate system is independent of the component's size. Changing
* the component's size with this method may scale/stretch the rendered
* graphics.
*/
#Override
public void setHeight(String height) {
HasSize.super.setHeight(height);
}
/**
* {#inheritDoc}
* <p>
* <b>NOTE:</b> Canvas has an internal coordinate system that it uses for
* drawing, and it uses the width and height provided in the constructor.
* This coordinate system is independent of the component's size. Changing
* the component's size with this method may scale/stretch the rendered
* graphics.
*/
#Override
public void setSizeFull() {
HasSize.super.setSizeFull();
}
public void addComponent(Label label) {
}
}
CanvasRenderingContext2D.java:
package com.vaadin.starter.beveragebuddy.ui.components;
import com.vaadin.starter.beveragebuddy.backend.MainLayout;
import java.io.Serializable;
import java.util.ArrayList;
/**
* The context for rendering shapes and images on a canvas.
* <p>
* This is a Java wrapper for the <a href=
* "https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D">same
* client-side API</a>.
*/
public class CanvasRenderingContext2D {
private Canvas canvas;
public static ArrayList<BoundingBox> arrayBoxes = new ArrayList<BoundingBox>();
protected CanvasRenderingContext2D(Canvas canvas) {
this.canvas = canvas;
}
public void setFillStyle(String fillStyle) {
setProperty("fillStyle", fillStyle);
}
public void setStrokeStyle(String strokeStyle) {
setProperty("strokeStyle", strokeStyle);
}
public void setLineWidth(double lineWidth) {
setProperty("lineWidth", lineWidth);
}
public void setFont(String font) {
setProperty("font", font);
}
public void arc(double x, double y, double radius, double startAngle,
double endAngle, boolean antiClockwise) {
callJsMethod("arc", x, y, radius, startAngle, endAngle, antiClockwise);
}
public void beginPath() {
callJsMethod("beginPath");
}
public void clearRect(double x, double y, double width, double height) {
callJsMethod("clearRect", x, y, width, height);
Canvas.arrayBoxes.clear();
System.out.println(arrayBoxes.toString());
MainLayout.count = 0;
}
public void closePath() {
callJsMethod("closePath");
}
/**
* Fetches the image from the given location and draws it on the canvas.
* <p>
* <b>NOTE:</b> The drawing will happen asynchronously after the browser has
* received the image.
*
* #param src
* the url of the image to draw
* #param x
* the x-coordinate of the top-left corner of the image
* #param y
* the y-coordinate of the top-left corner of the image
*/
public void drawImage(String src, double x, double y) {
runScript(String.format(
"var zwKqdZ = new Image();" + "zwKqdZ.onload = function () {"
+ "$0.getContext('2d').drawImage(zwKqdZ, %s, %s);};"
+ "zwKqdZ.src='%s';",
x, y, src));
}
/**
* Fetches the image from the given location and draws it on the canvas.
* <p>
* <b>NOTE:</b> The drawing will happen asynchronously after the browser has
* received the image.
*
* #param src
* the url of the image to draw
* #param x
* the x-coordinate of the top-left corner of the image
* #param y
* the y-coordinate of the top-left corner of the image
* #param width
* the width for the image
* #param height
* the height for the image
*/
public void drawImage(String src, double x, double y, double width,
double height) {
runScript(String.format("var zwKqdZ = new Image();"
+ "zwKqdZ.onload = function () {"
+ "$0.getContext('2d').drawImage(zwKqdZ, %s, %s, %s, %s);};"
+ "zwKqdZ.src='%s';", x, y, width, height, src));
}
public void fill() {
callJsMethod("fill");
}
public void fillRect(double x, double y, double width, double height) {
callJsMethod("fillRect", x, y, width, height);
}
public void fillText(String text, double x, double y) {
callJsMethod("fillText", text, x, y);
}
public void lineTo(double x, double y) {
callJsMethod("lineTo", x, y);
}
public void moveTo(double x, double y) {
callJsMethod("moveTo", x, y);
}
public void rect(double x, double y, double width, double height) {
callJsMethod("rect", x, y, width, height);
}
public void restore() {
callJsMethod("restore");
}
public void rotate(double angle) {
callJsMethod("rotate", angle);
}
public void save() {
callJsMethod("save");
}
public void scale(double x, double y) {
callJsMethod("scale", x, y);
}
public void stroke() {
callJsMethod("stroke");
}
public void strokeRect(double x, double y, double width, double height) {
callJsMethod("strokeRect", x, y, width, height);
}
public void strokeText(String text, double x, double y) {
callJsMethod("strokeText", text, x, y);
}
public void translate(double x, double y) {
callJsMethod("translate", x, y);
}
protected void setProperty(String propertyName, Serializable value) {
runScript(String.format("$0.getContext('2d').%s='%s'", propertyName,
value));
}
/**
* Runs the given js so that the execution order works with callJsMethod().
* Any $0 in the script will refer to the canvas element.
*/
private void runScript(String script) {
canvas.getElement().getNode().runWhenAttached(
// This structure is needed to make the execution order work
// with Element.callFunction() which is used in callJsMethod()
ui -> ui.getInternals().getStateTree().beforeClientResponse(
canvas.getElement().getNode(),
context -> ui.getPage().executeJavaScript(script,
canvas.getElement())));
}
protected void callJsMethod(String methodName, Serializable... parameters) {
canvas.getElement().callFunction("getContext('2d')." + methodName,
parameters);
}
}
BoundingBox.java:
package com.vaadin.starter.beveragebuddy.ui.components;
public class BoundingBox {
public double xcoordi = 0;
public double ycoordi = 0;
public double boxWidth = 0;
public double boxHeight = 0;
public String boxname;
public String boxcategory;
public BoundingBox(String boxname, String boxcategory, double xcoordi, double ycoordi, double boxWidth, double boxHeight) {
this.boxname = boxname;
this.boxcategory = boxcategory;
this.xcoordi = xcoordi;
this.ycoordi = ycoordi;
this.boxWidth = boxWidth;
this.boxHeight = boxHeight;
}
public String getBoxName() {
return boxname;
}
public void setName(String boxname) {
this.boxname = boxname;
}
public String getBoxcategory() {
return boxcategory;
}
public void setBoxcategory(String boxcategory) {
this.boxcategory = boxcategory;
}
public double getXcoordi() {
return xcoordi;
}
public void setXcoordi(double xcoordi) {
this.xcoordi = xcoordi;
}
public double getYcoordi() {
return ycoordi;
}
public void setYcoordi(double ycoordi) {
this.ycoordi = ycoordi;
}
public double getWidth() {
return boxWidth;
}
public void setWidth(double endX, double xcoordi) {
boxWidth = endX - xcoordi;
}
public double getHeight() {
return boxHeight;
}
public void setHeight(double endY, double ycoordi) {
boxHeight = endY - ycoordi;
}
#Override
public String toString() {
return "{" +
"Name=" + boxname +
", Class=" + boxcategory +
", X=" + xcoordi +
", Y=" + ycoordi +
", Width=" + boxWidth +
", Height=" + boxHeight +
'}';
}
}
MousePosition.java:
package com.vaadin.starter.beveragebuddy.ui.components;
public class MousePosition {
public double mouseX;
public double mouseY;
public MousePosition(double mouseX, double mouseY) {
this.mouseX = mouseX;
this.mouseY = mouseY;
}
public double getMouseX() {
return mouseX;
}
public void setMouseX(double mouseX) {
this.mouseX = mouseX;
}
public double getMouseY() {
return mouseY;
}
public void setMouseY(double mouseY) {
this.mouseY = mouseY;
}
#Override
public String toString() {
return "X = " + mouseX +
", Y = " + mouseY;
}
}
Stack trace:
[qtp1975873209-21] ERROR com.vaadin.flow.server.DefaultErrorHandler -
java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
at java.util.ArrayList.rangeCheck(ArrayList.java:657)
at java.util.ArrayList.get(ArrayList.java:433)
at com.vaadin.starter.beveragebuddy.ui.components.Canvas.undoLast(Canvas.java:191)
at com.vaadin.starter.beveragebuddy.backend.MainLayout.lambda$new$9b1b5227$1(MainLayout.java:67)
at com.vaadin.flow.component.ComponentEventBus.fireEvent(ComponentEventBus.java:133)
at com.vaadin.flow.component.ComponentEventBus.handleDomEvent(ComponentEventBus.java:327)
at com.vaadin.flow.component.ComponentEventBus.lambda$addDomTrigger$5ee67f2b$1(ComponentEventBus.java:191)
at com.vaadin.flow.internal.nodefeature.ElementListenerMap.lambda$fireEvent$2(ElementListenerMap.java:379)
at java.util.ArrayList.forEach(ArrayList.java:1257)
at com.vaadin.flow.internal.nodefeature.ElementListenerMap.fireEvent(ElementListenerMap.java:379)
at com.vaadin.flow.server.communication.rpc.EventRpcHandler.handleNode(EventRpcHandler.java:59)
at com.vaadin.flow.server.communication.rpc.AbstractRpcInvocationHandler.handle(AbstractRpcInvocationHandler.java:64)
at com.vaadin.flow.server.communication.ServerRpcHandler.handleInvocationData(ServerRpcHandler.java:377)
at com.vaadin.flow.server.communication.ServerRpcHandler.lambda$handleInvocations$0(ServerRpcHandler.java:367)
at java.util.ArrayList.forEach(ArrayList.java:1257)
at com.vaadin.flow.server.communication.ServerRpcHandler.handleInvocations(ServerRpcHandler.java:367)
at com.vaadin.flow.server.communication.ServerRpcHandler.handleRpc(ServerRpcHandler.java:309)
at com.vaadin.flow.server.communication.UidlRequestHandler.synchronizedHandleRequest(UidlRequestHandler.java:89)
at com.vaadin.flow.server.SynchronizedRequestHandler.handleRequest(SynchronizedRequestHandler.java:40)
at com.vaadin.flow.server.VaadinService.handleRequest(VaadinService.java:1487)
at com.vaadin.flow.server.VaadinServlet.service(VaadinServlet.java:300)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:790)
at org.eclipse.jetty.servlet.ServletHolder.handle(ServletHolder.java:812)
at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1669)
at org.eclipse.jetty.websocket.server.WebSocketUpgradeFilter.doFilter(WebSocketUpgradeFilter.java:201)
at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1652)
at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:585)
at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:143)
at org.eclipse.jetty.security.SecurityHandler.handle(SecurityHandler.java:577)
at org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:223)
at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1127)
at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:515)
at org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:185)
at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1061)
at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:141)
at org.eclipse.jetty.server.handler.ContextHandlerCollection.handle(ContextHandlerCollection.java:215)
at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:97)
at org.eclipse.jetty.server.Server.handle(Server.java:499)
at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:311)
at org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:258)
at org.eclipse.jetty.io.AbstractConnection$2.run(AbstractConnection.java:544)
at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:635)
at org.eclipse.jetty.util.thread.QueuedThreadPool$3.run(QueuedThreadPool.java:555)
at java.lang.Thread.run(Thread.java:748)
Any help is much appreciated, thank you!
In your undo method, you have this code
for (int i = 0; i < arrayBoxes.size(); i++){
context.clearRect(0, 0, 1580, 700);
context.beginPath();
context.setStrokeStyle("limegreen");
context.setLineWidth(2);
context.strokeRect(arrayBoxes.get(i).xcoordi, arrayBoxes.get(i).ycoordi, arrayBoxes.get(i).boxWidth, arrayBoxes.get(i).boxHeight);
context.fill();
}
Your context.clearRect() function contains the line
Canvas.arrayBoxes.clear();
Which removes all items from the arrayBoxes array. Therefore you get an exception at the end of the for loop when you try executing arrayBoxes.get(i).xcoordi etc.
I am trying to bounce my text in a window, inserted from a text file without modifying the inputs from the text. I have the code to create and bounce the text, but some of it is lost when trying to bounce on the wall. The code consists of 3 classes.
This creates the text:
public class MyShape extends ClosedShape {
//The width and height of the text (major and minor axis)
private int width, height;
private String bouncingText;
private double textFont;
/**
* Creates an oval.
* #param x The display component's x position.
* #param y The display component's y position.
* #param vx The display component's x velocity.
* #param vy The display component's y velocity.
* #param width The width of the text (in pixels).
* #param height The height of the text (in pixels).
* #param colour The line colour or fill colour.
* #param isFilled True if the oval is filled with colour, false if opaque.
*/
public MyShape (int insertionTime, int x, int y, int vx, int vy, String bouncingText, int width, int height, Color colour, boolean isFilled, double textFont) {
super (insertionTime, x, y, vx, vy, colour, isFilled);
this.width = width;
this.height = height;
this.bouncingText = bouncingText;
this.textFont = textFont;
}
/**
* Method to convert a text to a string.
*/
public String toString () {
String result = "This is a text shape\n";
result += super.toString ();
result += "Its width is " + this.width + " and its height is " + this.height + "\n";
return result;
}
/**
* #param width Resets the width.
*/
public void setWidth (int width) {
this.width = width;
}
/**
* #param height Resets the height.
*/
public void setHeight (int height) {
this.height = height;
}
public void setBouncingText (String bouncingText) {
this.bouncingText = bouncingText;
}
public void setTextFont (double textFont) {
this.textFont = textFont;
}
/**
* #return The width of the text.
*/
public int getWidth() {
return width;
}
/**
* #return The height of the text.
*/
public int getHeight() {
return height;
}
public String getBouncingText() {
return bouncingText;
}
public double getTextFont() {
return textFont;
}
public void pulsing() {
if (textFont > 20) {
textFont = 10;
}else {
textFont = 50;
}
}
/**
* Draw the text.
* #param g The graphics object of the drawable component.
*/
public void draw (GraphicsContext g) {
Font bouncingTextFont = new Font ("TimesNew", textFont);
g.setFill (colour);
g.setStroke( colour );
if (isFilled) {
g.fillText(bouncingText, xPos, yPos );
g.setFont(bouncingTextFont);
}
else {
g.strokeText(bouncingText, xPos, yPos );
g.setFont(bouncingTextFont);
}
}
}
This is the bouncing code:
import java.util.ArrayList;
import javafx.animation.Animation;
import javafx.animation.AnimationTimer;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.paint.Color;
import javafx.scene.shape.ArcType;
import javafx.stage.Stage;
import javafx.util.Duration;
public class BouncingShapesWindow {
private static final int ANIMATION_DELAY = 10;
private static final String FRAME_TITLE = "Shape Booooiiinggg Frame";
private GraphicsContext gc;
private Queue shapesToAdd;
private ArrayList<ClosedShape> activeShapes;
private int currentTime = 0;
private boolean flag=true;
private String filename;
public BouncingShapesWindow(GraphicsContext gc,String filename) {
this.gc=gc;
activeShapes=new ArrayList<ClosedShape>();
this.initShapes(filename);
this.insertShapes ();
drawShapes();
actionPerformed();
}
private void drawShapes () {
for (ClosedShape s : activeShapes)
{
s.draw(gc);
}
}
private void initShapes (String filename) {
shapesToAdd = ReadShapeFile.readFile(filename);
}
private void insertShapes() {
//no more shapes to add, we are done
if (shapesToAdd.isEmpty ()) {
return;
}
//add shapes if needed
ClosedShape current = (ClosedShape) shapesToAdd.peek ();
while (!shapesToAdd.isEmpty () && (current.getInsertionTime() <= currentTime*ANIMATION_DELAY)) {
activeShapes.add(current);
shapesToAdd.dequeue();
if (!shapesToAdd.isEmpty ()) {
current = (ClosedShape) shapesToAdd.peek();
}
}
}
public void actionPerformed()
{
Timeline timeline = new Timeline(new KeyFrame(Duration.millis(5), ae -> onTime()));
timeline.setCycleCount(Animation.INDEFINITE);
timeline.play();
}
private void onTime() {
currentTime++;
double h =gc.getCanvas().getHeight();
double w = gc.getCanvas().getWidth();
gc.clearRect(0, 0, w, h);
moveShapes();
insertShapes ();
drawShapes();
}
public void moveShapes()
{
double dimsY = gc.getCanvas().getHeight() ;
double dimsX = gc.getCanvas().getWidth() ;
for (ClosedShape s : activeShapes)
{
s.move();
// Move us back in and bounce if we went outside the drawing area.
if (s.outOfBoundsX(dimsX))
{
s.putInBoundsX(dimsX);
s.bounceX();
}
if (s.outOfBoundsY(dimsY))
{
s.putInBoundsY(dimsY);
s.bounceY();
}
}
}
}
and this is the super class:
import javafx.scene.paint.Color;
import javafx.scene.canvas.GraphicsContext;
/**
* A ClosedShape is any shape that can be drawn without
* taking a pencil off a piece of paper.
* It's representation on computer has a line colour
* and a position on the drawable screen component.
* It can be filled in with colour or opaque.
* This class is a super class for all shapes.
*/
public class ClosedShape {
/**
* The x position of the Shape.
*/
protected int xPos;
/**
* The y position of the Shape.
*/
protected int yPos = 20;
/**
* The x position of the Shape.
*/
protected int xVec;
/**
* The y position of the Shape.
*/
protected int yVec;
/**
* The line colour of the shape, or the filled in
* colour if the Shape has fill.
*/
protected Color colour;
/**
* Determines if the Shape has a fill colour or not.
*/
protected boolean isFilled;
/**
* Encodes the insertion time into the scene
*/
private int insertionTime;
/**
* Creates a closed shape object.
* #param x The x position.
* #param y the y position.
* #param colour The line or fill colour.
* #param isFilled True if the shape is filled, false if not.
*/
protected ClosedShape (int insertionTime, int x, int y, int vx, int vy, Color colour, boolean isFilled) {
this.xPos = x;
this.yPos = y;
this.xVec = vx;
this.yVec = vy;
this.colour = colour;
this.isFilled = isFilled;
this.insertionTime = insertionTime;
}
/**
* The method returns a string suitable for printing.
* #return string to print out shape.
*/
public String toString () {
String result = "";
result += "Its position is " + xPos + " " + yPos + "\n";
result += "Its velocity is " + xVec + " " + yVec + "\n";
result += "Its colour is " + colour + "\n";
if (isFilled)
result += "It is filled" + "\n";
else
result += "It is not filled" + "\n";
result += "It should be inserted at " + insertionTime + "\n";
return result;
}
/**
* Resets the x position.
*/
public void setX (int x) {
this.xPos = x;
}
/**
* Resets the y position.
*/
public void setY (int y) {
this.yPos = y;
}
/**
* Resets the x vector
*/
public void setVecX (int x) {
this.xVec = x;
}//end setVecX
/**
* Resets the y position.
*/
public void setVecY (int y) {
this.yVec = y;
}//end setVecY
/**
* Resets the colour.
*/
public void setColour (Color colour) {
this.colour = colour;
}
/**
* Sets the shape to filled.
*/
public void setFilled () {
isFilled = true;
}
/**
* Sets the shape to unfilled.
*/
public void unsetFilled () {
isFilled = false;
}
/**
* Sets the insertion time.
*/
public void setInsertionTime (int time) {
insertionTime = time;
}
/**
* #return The x position value.
*/
public int getX() {
return xPos;
}
/**
* #return The y position value.
*/
public int getY() {
return yPos;
}
/**
* #return The colour.
*/
public Color getColour() {
return colour;
}
/**
* #return True if the shape is filled, false if not.
*/
public boolean isFilled() {
return isFilled;
}
/**
* #return the insertion time.
*/
public int getInsertionTime () {
return insertionTime;
}
/**
* Puts the shape back in bounds in X
*/
public void putInBoundsX (double winX) {
if (xPos < 0) xPos = 0;
if (xPos + this.getWidth() > winX) {
xPos = (int) (winX - Math.ceil (this.getWidth ()));
}
}//end inBoundsX;
/**
* Puts the shape back in bounds
*/
public void putInBoundsY (double winY) {
if (yPos < 0) yPos = 0;
if (yPos + this.getHeight() > winY) {
yPos = (int) (winY - Math.ceil (this.getHeight ()));
}
}//end inBoundsY;
/**
* Bounces the shape off a vertical wall
*/
public void bounceX () {
xVec = -xVec;
}
/**
* Bounces the shape off a horizontal wall
*/
public void bounceY () {
yVec = -yVec;
}
/**
* Returns true if the shapes have gone out of bounds in X
*/
public boolean outOfBoundsX (double winX) {
return (xPos + this.getWidth()> winX) || (xPos < 0);
}
/**
* Returns true if the shapes have gone out of bounds in Y
*/
public boolean outOfBoundsY (double winY) {
return (yPos + this.getHeight() > winY) || (yPos < 0);
}
/**
* Takes in a direction and a velocity and moves the shape
* in that direction on unit
*/
public void move () {
xPos += xVec;
yPos += yVec;
}
/**
* Draws the object to the current component.
* #param g The graphics object associated with the drawing component.
*/
public void draw (GraphicsContext g) {
System.out.println ("You forgot to override this method! (draw)");
System.out.println ("Don't modify this method.");
}
/**
* Get the width of the current component
*/
public int getWidth () {
System.out.println ("You forgot to override this method! (getWidth)");
System.out.println ("Don't modify this method.");
return 1;
}
/**
* Get the width of the current component
*/
public int getHeight () {
System.out.println ("You forgot to override a method! (getHeight)");
System.out.println ("Don't modify this method.");
return 1;
}
public int getSide () {
System.out.println ("You forgot to override this method! (getSide)");
System.out.println ("Don't modify this method.");
return 1;
}
}
I tried reversing it, but nothing works. Using other shapes like circle or rect is OK, but with text it bounces under the head of the window and if I change the font size it loses its boundaries. I tried searching for solutions and it seems that everything uses JFrame and JPanel. I hope the code I provided leads to a useful solution.
This question already has answers here:
glTranslatef not working within glBegin .. glEnd
(2 answers)
Closed 7 years ago.
I'm attempting to draw a red square with LGJWL (native OpenGL bindings for Java) in Java. I receive a 1282 error from OpenGL and my square does not appear.
Below is (what I believe to be) the basic implementation for what I'm trying to do.
// begin drawing quads
glBegin(GL11.GL_QUADS);
// set color
glColor4d(255, 0, 0, 255);
// draw rectangle vertices
glVertex2i(0, 0);
glVertex2i(100, 0);
glVertex2i(100, 100);
glVertex2i(0, 100);
glEnd();
Following is what I'm trying to do in practice.
public class WindowComponent extends Component {
/** Window color */
private Color color = new Color();
/**
* Sets the window's color
* #param r red
* #param b blue
* #param g green
* #param a alpha
*/
public void setColor(int r, int g, int b, int a) {
color.set(r, g, b, a);
}
/**
* #see Component#renderComponent()
*/
#Override
protected void renderComponent() {
// begin drawing quads
begin(GL11.GL_QUADS);
// set color
GL11.glColor4ub(color.getRedByte(), color.getGreenByte(), color.getBlueByte(), color.getAlphaByte());
// draw rectangle vertices
GL11.glVertex2i(0, 0);
GL11.glVertex2i(getWidth(), 0);
GL11.glVertex2i(getWidth(), getHeight());
GL11.glVertex2i(0, getHeight());
end();
}
}
is an extension of...
public abstract class Component {
/** Dimension of component */
private Dimension size = new Dimension();
/** Position of component */
private Vector2f position = new Vector2f();
/**
* Gets width
* #return width
*/
public int getWidth() {
return size.getWidth();
}
/**
* Gets height
* #return height
*/
public int getHeight() {
return size.getHeight();
}
/**
* Gets size
* #return size
*/
public Dimension getSize() {
return size;
}
/**
* Gets X position
* #return X position
*/
public float getX() {
return position.getX();
}
/**
* Gets Y position
* #return Y position
*/
public float getY() {
return position.getY();
}
/**
* Gets position vector
* #return position vector
*/
public Vector2f getPosition() {
return position;
}
/**
* Sets width
* #param width width
*/
public void setWidth(int width) {
size.setWidth(width);
}
/**
* Sets height
* #param height height
*/
public void setHeight(int height) {
size.setHeight(height);
}
/**
* Sets size
* #param width width
* #param height height
*/
public void setSize(int width, int height) {
size.setSize(width, height);
}
/**
* Sets X position
* #param x X position
*/
public void setX(float x) {
position.setX(x);
}
/**
* Sets Y position
* #param y Y position
*/
public void setY(float y) {
position.setY(y);
}
/**
* Sets position
* #param x X position
* #param y Y position
*/
public void setPosition(float x, float y) {
position.set(x, y);
}
/**
* Begins drawing and does parent translations
* #param mode drawing mode
*/
protected void begin(int mode) {
GL11.glDisable(GL11.GL_TEXTURE_2D);
GL11.glBegin(mode);
GL11.glTranslatef(getX(), getY(), 0);
}
/**
* Ends drawing and does parent translations
*/
protected void end() {
GL11.glTranslatef(-getX(), -getY(), 0);
GL11.glEnd();
GL11.glEnable(GL11.GL_TEXTURE_2D);
}
/**
* Applies pre-rendering checks then renders the component
*/
public void render() {
renderComponent();
}
/**
* Component rendering code here
*/
protected abstract void renderComponent();
}
and I'm initializing this all with...
WindowComponent window = new WindowComponent();
window.setSize(100, 100);
window.setPosition(100, 100);
window.setColor(255, 0, 0, 255);
window.render();
In begin:
GL11.glBegin(mode);
GL11.glTranslatef(getX(), getY(), 0);
You cannot call glTranslate in between glBegin and glEnd; move it to before.
ok so I just started getting into 3D game programming I have a basic world where I have my own physics and basic controls however ever time I factor in speed for my movement it throws off my direction calculations and I don't know how to fix this
here is my camera class
package org.phin.platformer3d.game;
import org.lwjgl.input.Keyboard;
import org.lwjgl.opengl.GL11;
import org.lwjgl.util.glu.GLU;
import org.phin.platformer3d.lib.Strings;
import org.phin.platformer3d.object.GameObject;
public class Camera extends GameObject {
/**
* the field of view angle
*/
private float fovAngle;
/**
* the ratio between points on a object
*/
private float aspectRatio;
/**
* nearest clipping point
* (the near render distance so objects are not rendered into the camera
*/
private float near;
/**
* farthest clipping point (render distance)
*/
private float far;
private float amtJumped;
private boolean blockKeyUp = false;
private boolean blockKeyDown = false;
/**
* initializes the <code>field of view angle, aspect ratio, near view clipping and
* far clipping (render distance) </code>
*
* #param fovAngle
* #param ar
* #param near
* #param far
*
*/
public Camera(float fovAngle, float ar, float near, float far) {
this.fovAngle = fovAngle;
this.aspectRatio = ar;
this.near = near;
this.far = far;
this.initGL();
}
/**
* Initializes openGL
*/
private void initGL() {
GL11.glMatrixMode(GL11.GL_PROJECTION);
GL11.glLoadIdentity();
GLU.gluPerspective(this.fovAngle, this.aspectRatio, this.near, this.far);
GL11.glMatrixMode(GL11.GL_MODELVIEW);
GL11.glEnable(GL11.GL_DEPTH_TEST);
GL11.glEnable(GL11.GL_TEXTURE_2D);
}
/**
* creates and updates the camera view (called every frame)
*/
public void updateView() {
GL11.glRotatef(super.getRotX(), super.getX(), 0, 0);
GL11.glRotatef(super.getRotY(), 0, super.getY(), 0);
GL11.glRotatef(super.getRotZ(), 0, 0, super.getZ());
GL11.glTranslatef(super.getX(), super.getY(), super.getZ());
}
public void setAmtJumped(float amt) {
this.amtJumped = amt;
}
public void setKeyBlocked(String key, boolean b) {
if (key.equals("up")) {
this.blockKeyUp = b;
} else if (key.equals("down")) {
this.blockKeyDown = b;
}
}
/**
* translates the camera based on the user key input
*/
private void keyEvent() {
if (Keyboard.isKeyDown(Keyboard.KEY_W)) {
if (!this.blockKeyUp) {
this.blockKeyDown = false;
super.setX(super.getX() - (float) Math.cos(Math.toRadians(super.getRotY() + 90)));
super.setZ(super.getZ() + (float) Math.sin(Math.toRadians(super.getRotY() + 90)));
}
} else if (Keyboard.isKeyDown(Keyboard.KEY_S)) {
if (!this.blockKeyDown) {
this.blockKeyUp = false;
super.setX(super.getX() + (float) Math.cos(Math.toRadians(super.getRotY() + 90)));
super.setZ(super.getZ() - (float) Math.sin(Math.toRadians(super.getRotY() + 90)));
}
}
// rotate left or right
if (Keyboard.isKeyDown(Keyboard.KEY_D)) {
this.blockKeyUp = false;
this.blockKeyDown = false;
super.setRotY(super.getRotY() - Strings.SENSITIVITY);
} else if (Keyboard.isKeyDown(Keyboard.KEY_A)) {
this.blockKeyDown = false;
this.blockKeyUp = false;
super.setRotY(super.getRotY() + Strings.SENSITIVITY);
}
// Strife left or right
if (Keyboard.isKeyDown(Keyboard.KEY_E)) {
super.setX(super.getX() - (float) Math.cos(Math.toRadians(super.getRotY())));
super.setZ(super.getZ() + (float) Math.sin(Math.toRadians(super.getRotY())));
} else if (Keyboard.isKeyDown(Keyboard.KEY_Q)) {
super.setX(super.getX() - (float) Math.cos(Math.toRadians(super.getRotY() + 180)));
super.setZ(super.getZ() + (float) Math.sin(Math.toRadians(super.getRotY() + 180)));
}
if (Keyboard.isKeyDown(Keyboard.KEY_SPACE)) {
this.blockKeyDown = false;
this.blockKeyUp = false;
if (this.amtJumped < Strings.JUMP_HEIGHT - super.getHeight()) {
super.setY(super.getY() - 2.5F); // 2.5
this.amtJumped += 2.5F;
} else {
this.amtJumped = 10000;
}
}
}
public void update() {
this.keyEvent();
// camera gravity
super.setY(super.getY() + Strings.GRAVITY);
this.updateView();
}
// no need to render the camera
public void render() {}
}
the occurrences of
super.setX(super.getX() - (float) Math.cos(Math.toRadians(super.getRotY())));
super.setZ(super.getZ() + (float) Math.sin(Math.toRadians(super.getRotY())));
are where I need to add the speed but when I do it throws of the calculation. How can I add speed without throwing it off?
You need to multiply your direction by your speed, not add as i'm guessing your doing now. try this:
super.setX(super.getX() - (float) (speed*Math.cos(Math.toRadians(super.getRotY()))));
super.setZ(super.getZ() + (float) (speed*Math.sin(Math.toRadians(super.getRotY()))));
I have the following NavigableImagePanel, it is under BSD license and I found it in the web. What I want to do with this panel is as follow:
I want to add a JScrollPane to it in order to show images in their full size and let the users to re-center the image using the small navigation panel. Right now, the panel resize the images to fit them in the current panel size. I want it to load the image in its real size and let users to navigate to different parts of the image using the navigation panel.
Source code for the panel:
import java.awt.AWTEvent;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GraphicsEnvironment;
import java.awt.Image;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Toolkit;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
/**
* #author pxt
*
*/
public class NavigableImagePanel extends JPanel {
/**
* <p>Identifies a change to the zoom level.</p>
*/
public static final String ZOOM_LEVEL_CHANGED_PROPERTY = "zoomLevel";
/**
* <p>Identifies a change to the zoom increment.</p>
*/
public static final String ZOOM_INCREMENT_CHANGED_PROPERTY = "zoomIncrement";
/**
* <p>Identifies that the image in the panel has changed.</p>
*/
public static final String IMAGE_CHANGED_PROPERTY = "image";
private static final double SCREEN_NAV_IMAGE_FACTOR = 0.15; // 15% of panel's width
private static final double NAV_IMAGE_FACTOR = 0.3; // 30% of panel's width
private static final double HIGH_QUALITY_RENDERING_SCALE_THRESHOLD = 1.0;
private static final Object INTERPOLATION_TYPE =
RenderingHints.VALUE_INTERPOLATION_BILINEAR;
private double zoomIncrement = 0.2;
private double zoomFactor = 1.0 + zoomIncrement;
private double navZoomFactor = 1.0 + zoomIncrement;
private BufferedImage image;
private BufferedImage navigationImage;
private int navImageWidth;
private int navImageHeight;
private double initialScale = 0.0;
private double scale = 0.0;
private double navScale = 0.0;
private int originX = 0;
private int originY = 0;
private Point mousePosition;
private Dimension previousPanelSize;
private boolean navigationImageEnabled = true;
private boolean highQualityRenderingEnabled = true;
private WheelZoomDevice wheelZoomDevice = null;
private ButtonZoomDevice buttonZoomDevice = null;
/**
* <p>Defines zoom devices.</p>
*/
public static class ZoomDevice {
/**
* <p>Identifies that the panel does not implement zooming,
* but the component using the panel does (programmatic zooming method).</p>
*/
public static final ZoomDevice NONE = new ZoomDevice("none");
/**
* <p>Identifies the left and right mouse buttons as the zooming device.</p>
*/
public static final ZoomDevice MOUSE_BUTTON = new ZoomDevice("mouseButton");
/**
* <p>Identifies the mouse scroll wheel as the zooming device.</p>
*/
public static final ZoomDevice MOUSE_WHEEL = new ZoomDevice("mouseWheel");
private String zoomDevice;
private ZoomDevice(String zoomDevice) {
this.zoomDevice = zoomDevice;
}
public String toString() {
return zoomDevice;
}
}
//This class is required for high precision image coordinates translation.
private class Coords {
public double x;
public double y;
public Coords(double x, double y) {
this.x = x;
this.y = y;
}
public int getIntX() {
return (int)Math.round(x);
}
public int getIntY() {
return (int)Math.round(y);
}
public String toString() {
return "[Coords: x=" + x + ",y=" + y + "]";
}
}
private class WheelZoomDevice implements MouseWheelListener {
public void mouseWheelMoved(MouseWheelEvent e) {
Point p = e.getPoint();
boolean zoomIn = (e.getWheelRotation() < 0);
if (isInNavigationImage(p)) {
if (zoomIn) {
navZoomFactor = 1.0 + zoomIncrement;
} else {
navZoomFactor = 1.0 - zoomIncrement;
}
zoomNavigationImage();
} else if (isInImage(p)) {
if (zoomIn) {
zoomFactor = 1.0 + zoomIncrement;
} else {
zoomFactor = 1.0 - zoomIncrement;
}
zoomImage();
}
}
}
private class ButtonZoomDevice extends MouseAdapter {
public void mouseClicked(MouseEvent e) {
Point p = e.getPoint();
if (SwingUtilities.isRightMouseButton(e)) {
if (isInNavigationImage(p)) {
navZoomFactor = 1.0 - zoomIncrement;
zoomNavigationImage();
} else if (isInImage(p)) {
zoomFactor = 1.0 - zoomIncrement;
zoomImage();
}
} else {
if (isInNavigationImage(p)) {
navZoomFactor = 1.0 + zoomIncrement;
zoomNavigationImage();
} else if (isInImage(p)) {
zoomFactor = 1.0 + zoomIncrement;
zoomImage();
}
}
}
}
/**
* <p>Creates a new navigable image panel with no default image and
* the mouse scroll wheel as the zooming device.</p>
*/
public NavigableImagePanel() {
setOpaque(false);
addComponentListener(new ComponentAdapter() {
public void componentResized(ComponentEvent e) {
if (scale > 0.0) {
if (isFullImageInPanel()) {
centerImage();
} else if (isImageEdgeInPanel()) {
scaleOrigin();
}
if (isNavigationImageEnabled()) {
createNavigationImage();
}
repaint();
}
previousPanelSize = getSize();
}
});
addMouseListener(new MouseAdapter() {
public void mousePressed(MouseEvent e) {
if (SwingUtilities.isLeftMouseButton(e)) {
if (isInNavigationImage(e.getPoint())) {
Point p = e.getPoint();
displayImageAt(p);
}
}
}
public void mouseClicked(MouseEvent e){
if (e.getClickCount() == 2) {
resetImage();
}
}
});
addMouseMotionListener(new MouseMotionListener() {
public void mouseDragged(MouseEvent e) {
if (SwingUtilities.isLeftMouseButton(e)
&& !isInNavigationImage(e.getPoint())) {
Point p = e.getPoint();
moveImage(p);
}
}
public void mouseMoved(MouseEvent e) {
//we need the mouse position so that after zooming
//that position of the image is maintained
mousePosition = e.getPoint();
}
});
setZoomDevice(ZoomDevice.MOUSE_WHEEL);
}
/**
* <p>Creates a new navigable image panel with the specified image
* and the mouse scroll wheel as the zooming device.</p>
*/
public NavigableImagePanel(BufferedImage image) throws IOException {
this();
setImage(image);
}
private void addWheelZoomDevice() {
if (wheelZoomDevice == null) {
wheelZoomDevice = new WheelZoomDevice();
addMouseWheelListener(wheelZoomDevice);
}
}
private void addButtonZoomDevice() {
if (buttonZoomDevice == null) {
buttonZoomDevice = new ButtonZoomDevice();
addMouseListener(buttonZoomDevice);
}
}
private void removeWheelZoomDevice() {
if (wheelZoomDevice != null) {
removeMouseWheelListener(wheelZoomDevice);
wheelZoomDevice = null;
}
}
private void removeButtonZoomDevice() {
if (buttonZoomDevice != null) {
removeMouseListener(buttonZoomDevice);
buttonZoomDevice = null;
}
}
/**
* <p>Sets a new zoom device.</p>
*
* #param newZoomDevice specifies the type of a new zoom device.
*/
public void setZoomDevice(ZoomDevice newZoomDevice) {
if (newZoomDevice == ZoomDevice.NONE) {
removeWheelZoomDevice();
removeButtonZoomDevice();
} else if (newZoomDevice == ZoomDevice.MOUSE_BUTTON) {
removeWheelZoomDevice();
addButtonZoomDevice();
} else if (newZoomDevice == ZoomDevice.MOUSE_WHEEL) {
removeButtonZoomDevice();
addWheelZoomDevice();
}
}
/**
* <p>Gets the current zoom device.</p>
*/
public ZoomDevice getZoomDevice() {
if (buttonZoomDevice != null) {
return ZoomDevice.MOUSE_BUTTON;
} else if (wheelZoomDevice != null) {
return ZoomDevice.MOUSE_WHEEL;
} else {
return ZoomDevice.NONE;
}
}
//Called from paintComponent() when a new image is set.
private void initializeParams() {
double xScale = (double)getWidth() / image.getWidth();
double yScale = (double)getHeight() / image.getHeight();
initialScale = Math.min(xScale, yScale);
scale = initialScale;
//An image is initially centered
centerImage();
if (isNavigationImageEnabled()) {
createNavigationImage();
}
}
//Centers the current image in the panel.
private void centerImage() {
originX = (int)(getWidth() - getScreenImageWidth()) / 2;
originY = (int)(getHeight() - getScreenImageHeight()) / 2;
}
//Creates and renders the navigation image in the upper let corner of the panel.
private void createNavigationImage() {
//We keep the original navigation image larger than initially
//displayed to allow for zooming into it without pixellation effect.
navImageWidth = (int)(getWidth() * NAV_IMAGE_FACTOR);
navImageHeight = navImageWidth * image.getHeight() / image.getWidth();
int scrNavImageWidth = (int)(getWidth() * SCREEN_NAV_IMAGE_FACTOR);
int scrNavImageHeight = scrNavImageWidth * image.getHeight() / image.getWidth();
navScale = (double)scrNavImageWidth / navImageWidth;
navigationImage = new BufferedImage(navImageWidth, navImageHeight,
image.getType());
Graphics g = navigationImage.getGraphics();
g.drawImage(image, 0, 0, navImageWidth, navImageHeight, null);
}
/**
* <p>Sets an image for display in the panel.</p>
*
* #param image an image to be set in the panel
*/
public void setImage(BufferedImage image) {
BufferedImage oldImage = this.image;
this.image = image;
//Reset scale so that initializeParameters() is called in paintComponent()
//for the new image.
scale = 0.0;
firePropertyChange(IMAGE_CHANGED_PROPERTY, (Image)oldImage, (Image)image);
repaint();
}
/**
* <p>resets an image to the centre of the panel</p>
*
*/
public void resetImage() {
BufferedImage oldImage = this.image;
this.image = image;
//Reset scale so that initializeParameters() is called in paintComponent()
//for the new image.
scale = 0.0;
firePropertyChange(IMAGE_CHANGED_PROPERTY, (Image)oldImage, (Image)image);
repaint();
}
/**
* <p>Tests whether an image uses the standard RGB color space.</p>
*/
public static boolean isStandardRGBImage(BufferedImage bImage) {
return bImage.getColorModel().getColorSpace().isCS_sRGB();
}
//Converts this panel's coordinates into the original image coordinates
private Coords panelToImageCoords(Point p) {
return new Coords((p.x - originX) / scale, (p.y - originY) / scale);
}
//Converts the original image coordinates into this panel's coordinates
private Coords imageToPanelCoords(Coords p) {
return new Coords((p.x * scale) + originX, (p.y * scale) + originY);
}
//Converts the navigation image coordinates into the zoomed image coordinates
private Point navToZoomedImageCoords(Point p) {
int x = p.x * getScreenImageWidth() / getScreenNavImageWidth();
int y = p.y * getScreenImageHeight() / getScreenNavImageHeight();
return new Point(x, y);
}
//The user clicked within the navigation image and this part of the image
//is displayed in the panel.
//The clicked point of the image is centered in the panel.
private void displayImageAt(Point p) {
Point scrImagePoint = navToZoomedImageCoords(p);
originX = -(scrImagePoint.x - getWidth() / 2);
originY = -(scrImagePoint.y - getHeight() / 2);
repaint();
}
//Tests whether a given point in the panel falls within the image boundaries.
private boolean isInImage(Point p) {
Coords coords = panelToImageCoords(p);
int x = coords.getIntX();
int y = coords.getIntY();
return (x >= 0 && x < image.getWidth() && y >= 0 && y < image.getHeight());
}
//Tests whether a given point in the panel falls within the navigation image
//boundaries.
private boolean isInNavigationImage(Point p) {
return (isNavigationImageEnabled() && p.x < getScreenNavImageWidth()
&& p.y < getScreenNavImageHeight());
}
//Used when the image is resized.
private boolean isImageEdgeInPanel() {
if (previousPanelSize == null) {
return false;
}
return (originX > 0 && originX < previousPanelSize.width
|| originY > 0 && originY < previousPanelSize.height);
}
//Tests whether the image is displayed in its entirety in the panel.
private boolean isFullImageInPanel() {
return (originX >= 0 && (originX + getScreenImageWidth()) < getWidth()
&& originY >= 0 && (originY + getScreenImageHeight()) < getHeight());
}
/**
* <p>Indicates whether the high quality rendering feature is enabled.</p>
*
* #return true if high quality rendering is enabled, false otherwise.
*/
public boolean isHighQualityRenderingEnabled() {
return highQualityRenderingEnabled;
}
/**
* <p>Enables/disables high quality rendering.</p>
*
* #param enabled enables/disables high quality rendering
*/
public void setHighQualityRenderingEnabled(boolean enabled) {
highQualityRenderingEnabled = enabled;
}
//High quality rendering kicks in when when a scaled image is larger
//than the original image. In other words,
//when image decimation stops and interpolation starts.
private boolean isHighQualityRendering() {
return (highQualityRenderingEnabled
&& scale > HIGH_QUALITY_RENDERING_SCALE_THRESHOLD);
}
/**
* <p>Indicates whether navigation image is enabled.<p>
*
* #return true when navigation image is enabled, false otherwise.
*/
public boolean isNavigationImageEnabled() {
return navigationImageEnabled;
}
/**
* <p>Enables/disables navigation with the navigation image.</p>
* <p>Navigation image should be disabled when custom, programmatic navigation
* is implemented.</p>
*
* #param enabled true when navigation image is enabled, false otherwise.
*/
public void setNavigationImageEnabled(boolean enabled) {
navigationImageEnabled = enabled;
repaint();
}
//Used when the panel is resized
private void scaleOrigin() {
originX = originX * getWidth() / previousPanelSize.width;
originY = originY * getHeight() / previousPanelSize.height;
repaint();
}
//Converts the specified zoom level to scale.
private double zoomToScale(double zoom) {
return initialScale * zoom;
}
/**
* <p>Gets the current zoom level.</p>
*
* #return the current zoom level
*/
public double getZoom() {
return scale / initialScale;
}
/**
* <p>Sets the zoom level used to display the image.</p>
* <p>This method is used in programmatic zooming. The zooming center is
* the point of the image closest to the center of the panel.
* After a new zoom level is set the image is repainted.</p>
*
* #param newZoom the zoom level used to display this panel's image.
*/
public void setZoom(double newZoom) {
Point zoomingCenter = new Point(getWidth() / 2, getHeight() / 2);
setZoom(newZoom, zoomingCenter);
}
/**
* <p>Sets the zoom level used to display the image, and the zooming center,
* around which zooming is done.</p>
* <p>This method is used in programmatic zooming.
* After a new zoom level is set the image is repainted.</p>
*
* #param newZoom the zoom level used to display this panel's image.
*/
public void setZoom(double newZoom, Point zoomingCenter) {
Coords imageP = panelToImageCoords(zoomingCenter);
if (imageP.x < 0.0) {
imageP.x = 0.0;
}
if (imageP.y < 0.0) {
imageP.y = 0.0;
}
if (imageP.x >= image.getWidth()) {
imageP.x = image.getWidth() - 1.0;
}
if (imageP.y >= image.getHeight()) {
imageP.y = image.getHeight() - 1.0;
}
Coords correctedP = imageToPanelCoords(imageP);
double oldZoom = getZoom();
scale = zoomToScale(newZoom);
Coords panelP = imageToPanelCoords(imageP);
originX += (correctedP.getIntX() - (int)panelP.x);
originY += (correctedP.getIntY() - (int)panelP.y);
firePropertyChange(ZOOM_LEVEL_CHANGED_PROPERTY, new Double(oldZoom),
new Double(getZoom()));
repaint();
}
/**
* <p>Gets the current zoom increment.</p>
*
* #return the current zoom increment
*/
public double getZoomIncrement() {
return zoomIncrement;
}
/**
* <p>Sets a new zoom increment value.</p>
*
* #param newZoomIncrement new zoom increment value
*/
public void setZoomIncrement(double newZoomIncrement) {
double oldZoomIncrement = zoomIncrement;
zoomIncrement = newZoomIncrement;
firePropertyChange(ZOOM_INCREMENT_CHANGED_PROPERTY,
new Double(oldZoomIncrement), new Double(zoomIncrement));
}
//Zooms an image in the panel by repainting it at the new zoom level.
//The current mouse position is the zooming center.
private void zoomImage() {
Coords imageP = panelToImageCoords(mousePosition);
double oldZoom = getZoom();
scale *= zoomFactor;
Coords panelP = imageToPanelCoords(imageP);
originX += (mousePosition.x - (int)panelP.x);
originY += (mousePosition.y - (int)panelP.y);
firePropertyChange(ZOOM_LEVEL_CHANGED_PROPERTY, new Double(oldZoom),
new Double(getZoom()));
repaint();
}
//Zooms the navigation image
private void zoomNavigationImage() {
navScale *= navZoomFactor;
repaint();
}
/**
* <p>Gets the image origin.</p>
* <p>Image origin is defined as the upper, left corner of the image in
* the panel's coordinate system.</p>
* #return the point of the upper, left corner of the image in the panel's coordinates
* system.
*/
public Point getImageOrigin() {
return new Point(originX, originY);
}
/**
* <p>Sets the image origin.</p>
* <p>Image origin is defined as the upper, left corner of the image in
* the panel's coordinate system. After a new origin is set, the image is repainted.
* This method is used for programmatic image navigation.</p>
I've used this component before myself, but never in exactly the way you describe. There is an existing function in the code, setZoom(), that can be used to programatically set the zoom level and repaint the image. I would recommend that you experiment with that function and the zoom level.