/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package websocket.drawboard;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.geom.Arc2D;
import java.awt.geom.Line2D;
import java.awt.geom.Rectangle2D;
/**
* A message that represents a drawing action.
* Note that we use primitive types instead of Point, Color etc.
* to reduce object allocation.<br><br>
*
* TODO: But a Color objects needs to be created anyway for drawing this
* onto a Graphics2D object, so this probably does not save much.
*/
public final class DrawMessage {
private int type;
private byte colorR, colorG, colorB, colorA;
private double thickness;
private double x1, y1, x2, y2;
/**
* The type.
*
* @return 1: Brush<br>2: Line<br>3: Rectangle<br>4: Ellipse
*/
public int getType() {
return type;
}
public void setType(int type) {
this.type = type;
}
public double getThickness() {
return thickness;
}
public void setThickness(double thickness) {
this.thickness = thickness;
}
public byte getColorR() {
return colorR;
}
public void setColorR(byte colorR) {
this.colorR = colorR;
}
public byte getColorG() {
return colorG;
}
public void setColorG(byte colorG) {
this.colorG = colorG;
}
public byte getColorB() {
return colorB;
}
public void setColorB(byte colorB) {
this.colorB = colorB;
}
public byte getColorA() {
return colorA;
}
public void setColorA(byte colorA) {
this.colorA = colorA;
}
public double getX1() {
return x1;
}
public void setX1(double x1) {
this.x1 = x1;
}
public double getX2() {
return x2;
}
public void setX2(double x2) {
this.x2 = x2;
}
public double getY1() {
return y1;
}
public void setY1(double y1) {
this.y1 = y1;
}
public double getY2() {
return y2;
}
public void setY2(double y2) {
this.y2 = y2;
}
public DrawMessage(int type, byte colorR, byte colorG, byte colorB,
byte colorA, double thickness, double x1, double x2, double y1,
double y2) {
this.type = type;
this.colorR = colorR;
this.colorG = colorG;
this.colorB = colorB;
this.colorA = colorA;
this.thickness = thickness;
this.x1 = x1;
this.x2 = x2;
this.y1 = y1;
this.y2 = y2;
}
/**
* Draws this DrawMessage onto the given Graphics2D.
*
* @param g The target for the DrawMessage
*/
public void draw(Graphics2D g) {
g.setStroke(new BasicStroke((float) thickness,
BasicStroke.CAP_ROUND, BasicStroke.JOIN_MITER));
g.setColor(new Color(colorR & 0xFF, colorG & 0xFF, colorB & 0xFF,
colorA & 0xFF));
if (x1 == x2 && y1 == y2) {
// Always draw as arc to meet the behavior in the HTML5 Canvas.
Arc2D arc = new Arc2D.Double(x1, y1, 0, 0,
0d, 360d, Arc2D.OPEN);
g.draw(arc);
} else if (type == 1 || type == 2) {
// Draw a line.
Line2D line = new Line2D.Double(x1, y1, x2, y2);
g.draw(line);
} else if (type == 3 || type == 4) {
double x1 = this.x1, x2 = this.x2,
y1 = this.y1, y2 = this.y2;
if (x1 > x2) {
x1 = this.x2;
x2 = this.x1;
}
if (y1 > y2) {
y1 = this.y2;
y2 = this.y1;
}
// TODO: If (x1 == x2 || y1 == y2) draw as line.
if (type == 3) {
// Draw a rectangle.
Rectangle2D rect = new Rectangle2D.Double(x1, y1,
x2 - x1, y2 - y1);
g.draw(rect);
} else if (type == 4) {
// Draw an ellipse.
Arc2D arc = new Arc2D.Double(x1, y1, x2 - x1, y2 - y1,
0d, 360d, Arc2D.OPEN);
g.draw(arc);
}
}
}
/**
* Converts this message into a String representation that
* can be sent over WebSocket.<br>
* Since a DrawMessage consists only of numbers,
* we concatenate those numbers with a ",".
*/
@Override
public String toString() {
return type + "," + (colorR & 0xFF) + "," + (colorG & 0xFF) + ","
+ (colorB & 0xFF) + "," + (colorA & 0xFF) + "," + thickness
+ "," + x1 + "," + y1 + "," + x2 + "," + y2;
}
public static DrawMessage parseFromString(String str)
throws ParseException {
int type;
byte[] colors = new byte[4];
double thickness;
double[] coords = new double[4];
try {
String[] elements = str.split(",");
type = Integer.parseInt(elements[0]);
if (!(type >= 1 && type <= 4))
throw new ParseException("Invalid type: " + type);
for (int i = 0; i < colors.length; i++) {
colors[i] = (byte) Integer.parseInt(elements[1 + i]);
}
thickness = Double.parseDouble(elements[5]);
if (Double.isNaN(thickness) || thickness < 0 || thickness > 100)
throw new ParseException("Invalid thickness: " + thickness);
for (int i = 0; i < coords.length; i++) {
coords[i] = Double.parseDouble(elements[6 + i]);
if (Double.isNaN(coords[i]))
throw new ParseException("Invalid coordinate: "
+ coords[i]);
}
} catch (RuntimeException ex) {
throw new ParseException(ex);
}
DrawMessage m = new DrawMessage(type, colors[0], colors[1],
colors[2], colors[3], thickness, coords[0], coords[2],
coords[1], coords[3]);
return m;
}
public static class ParseException extends Exception {
private static final long serialVersionUID = -6651972769789842960L;
public ParseException(Throwable root) {
super(root);
}
public ParseException(String message) {
super(message);
}
}
}