Skip to content

Commit 547f4e9

Browse files
authored
Merge pull request #72 from team5499/dev/limelight
Camera Widget
2 parents e6401c7 + 1cc6df1 commit 547f4e9

7 files changed

Lines changed: 244 additions & 1 deletion

File tree

build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ dependencies {
2727
}
2828

2929
group = 'org.team5499'
30-
version = '0.9.0' /* Change this when deploying a new version */
30+
version = '0.10.0' /* Change this when deploying a new version */
3131

3232
task sourcesJar(type: Jar) {
3333
from sourceSets.main.allJava

src/main/resources/page.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,4 +47,5 @@
4747
<script src="/javascript/widgets/double-editor.jsx" data-plugins="transform-es2015-modules-umd" type="text/babel"></script>
4848
<script src="/javascript/widgets/pidf-widget.jsx" data-plugins="transform-es2015-modules-umd" type="text/babel"></script>
4949
<script src="/javascript/widgets/pid-widget.jsx" data-plugins="transform-es2015-modules-umd" type="text/babel"></script>
50+
<script src="/javascript/widgets/camera-widget.jsx" data-plugins="transform-es2015-modules-umd" type="text/babel"></script>
5051
{% endblock %}
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
import PageUtils from 'page-utils';
2+
import Widget from 'widget';
3+
4+
class CameraWidget {};
5+
6+
CameraWidget.Body = class extends Widget.Body {
7+
init() {
8+
let src = this.widgetConfig.kwargs.src;
9+
this.state = {
10+
src: src,
11+
height: this.widgetConfig.height,
12+
imgStart: this.widgetConfig.kwargs.imgStart,
13+
imgSize: this.widgetConfig.kwargs.imgSize
14+
}
15+
this.divImg = React.createRef();
16+
this.imgDim = {x: 1, y: 1};
17+
}
18+
19+
settingsSave(newConfig) {
20+
let newSrc = newConfig.kwargs.src;
21+
this.setState({
22+
src: newSrc,
23+
height: newConfig.height,
24+
imgStart: newConfig.kwargs.imgStart,
25+
imgSize: newConfig.kwargs.imgSize
26+
});
27+
this.forceUpdate();
28+
}
29+
30+
componentDidMount() {
31+
this.getMeta(this.state.src).then((dim) => {
32+
this.imgDim = dim;
33+
34+
let divWidth = $(this.divImg.current).width();
35+
let divHeight = $(this.divImg.current).height();
36+
let imgWidth = this.imgDim.x * (divWidth / this.state.imgSize.x);
37+
let imgHeight = this.imgDim.y * (divHeight / this.state.imgSize.y);
38+
let imgOffsetX = -this.state.imgStart.x * (divWidth / this.state.imgSize.x);
39+
let imgOffsetY = -this.state.imgStart.y * (divHeight / this.state.imgSize.y);
40+
41+
$(this.divImg.current).css({backgroundSize: imgWidth + "px " + imgHeight + "px", backgroundPosition: imgOffsetX + "px " + imgOffsetY + "px"});
42+
});
43+
}
44+
45+
componentDidUpdate() {
46+
let divWidth = $(this.divImg.current).width();
47+
let divHeight = $(this.divImg.current).height();
48+
let imgWidth = this.imgDim.x * (divWidth / this.state.imgSize.x);
49+
let imgHeight = this.imgDim.y * (divHeight / this.state.imgSize.y);
50+
let imgOffsetX = -this.state.imgStart.x * (divWidth / this.state.imgSize.x);
51+
let imgOffsetY = -this.state.imgStart.y * (divHeight / this.state.imgSize.y);
52+
53+
$(this.divImg.current).css({backgroundSize: imgWidth + "px " + imgHeight + "px", backgroundPosition: imgOffsetX + "px " + imgOffsetY + "px"});
54+
}
55+
56+
getMeta(url){
57+
return new Promise((resolve, reject) => {
58+
var img = new Image();
59+
window.setTimeout(() => {
60+
reject();
61+
}, 3000);
62+
img.addEventListener("load", function(){
63+
resolve({x: this.naturalWidth, y: this.naturalHeight});
64+
img.src = "";
65+
});
66+
img.src = url;
67+
});
68+
}
69+
70+
render() {
71+
let heightPrefix = this.state.height.substr(parseFloat(this.state.height).toString().length, this.state.height.length);
72+
let imgWidth = this.state.imgSize[0]
73+
return <div ref={this.divImg} style={{background: "url(\"" + this.state.src + "\")", width: "100%", height: (parseFloat(this.state.height) - 60) + heightPrefix, overflow: "hidden"}} />;
74+
}
75+
}
76+
77+
CameraWidget.Settings = class extends Widget.Settings {
78+
init() {
79+
this.state = {
80+
src: this.widgetConfig.kwargs.src,
81+
imgStart: this.widgetConfig.kwargs.imgStart,
82+
imgSize: this.widgetConfig.kwargs.imgSize
83+
}
84+
}
85+
86+
settingsData(config) {
87+
let newConfig = config;
88+
newConfig.kwargs.src = this.state.src;
89+
newConfig.kwargs.imgStart = this.state.imgStart;
90+
newConfig.kwargs.imgSize = this.state.imgSize;
91+
return newConfig;
92+
}
93+
94+
onSrcEdit(e) {
95+
this.setState({
96+
src: e.target.value
97+
});
98+
}
99+
100+
onStartXEdit(e) {
101+
let newImgStart = this.state.imgStart;
102+
newImgStart.x = (e.target.value === "") ? 0 : parseInt(e.target.value);
103+
this.setState({imgStart: newImgStart});
104+
}
105+
106+
onStartYEdit(e) {
107+
let newImgStart = this.state.imgStart;
108+
newImgStart.y = (e.target.value === "") ? 0 : parseInt(e.target.value);
109+
this.setState({imgStart: newImgStart});
110+
}
111+
112+
onSizeXEdit(e) {
113+
let newImgSize = this.state.imgSize;
114+
newImgSize.x = (e.target.value === "") ? 0 : parseInt(e.target.value);
115+
this.setState({imgSize: newImgSize});
116+
}
117+
118+
onSizeYEdit(e) {
119+
let newImgSize = this.state.imgSize;
120+
newImgSize.y = (e.target.value === "") ? 0 : parseInt(e.target.value);
121+
this.setState({imgSize: newImgSize});
122+
}
123+
124+
render() {
125+
return [
126+
<input key="src" className='form-control mb-2' type='text' placeholder='img source' value={this.state.src} onChange={(e) => this.onSrcEdit(e)} />,
127+
<input key="startx" className='form-control mb-2' type='number' step='1' placeholder='image start x' value={this.state.imgStart.x} onChange={(e) => this.onStartXEdit(e)} />,
128+
<input key="starty" className='form-control mb-2' type='number' step='1' placeholder='image start y' value={this.state.imgStart.y} onChange={(e) => this.onStartYEdit(e)} />,
129+
<input key="sizex" className='form-control mb-2' type='number' step='1' placeholder='image size x' value={this.state.imgSize.x} onChange={(e) => this.onSizeXEdit(e)} />,
130+
<input key="sizey" className='form-control mb-2' type='number' step='1' placeholder='image size y' value={this.state.imgSize.y} onChange={(e) => this.onSizeYEdit(e)} />
131+
];
132+
}
133+
}
134+
135+
export default CameraWidget;
136+
137+
// make sure to do this for every widget
138+
PageUtils.addWidgetClass('CameraWidget', CameraWidget);
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
package tests
2+
3+
import kotlin.text.toByteArray
4+
5+
import javax.imageio.ImageIO
6+
import java.awt.image.BufferedImage
7+
import java.io.OutputStream
8+
import java.io.ByteArrayOutputStream
9+
import java.io.IOException
10+
import java.net.ServerSocket
11+
import java.net.Socket
12+
13+
class ImageServer(imag: BufferedImage) : Runnable {
14+
15+
private lateinit var img: BufferedImage
16+
private lateinit var serverSocket: ServerSocket
17+
private lateinit var socket: Socket
18+
private val boundary = "stream"
19+
private lateinit var outputStream: OutputStream
20+
public var toPush: BufferedImage = imag
21+
private var closes = 0
22+
23+
fun startStreamingServer() {
24+
serverSocket = ServerSocket(5801)
25+
socket = serverSocket.accept()
26+
writeHeader(socket.getOutputStream(), boundary)
27+
}
28+
29+
fun writeHeader(stream: OutputStream, boundary: String) {
30+
stream.write(("HTTP/1.0 200 OK\r\n" +
31+
"Connection: close\r\n" +
32+
"Max-Age: 0\r\n" +
33+
"Expires: 0\r\n" +
34+
"Cache-Control: no-store, no-cache, must-revalidate, pre-check=0, post-check=0, max-age=0\r\n" +
35+
"Pragma: no-cache\r\n" +
36+
"Content-Type: multipart/x-mixed-replace; " +
37+
"boundary=" + boundary + "\r\n" +
38+
"\r\n" +
39+
"--" + boundary + "\r\n").toByteArray())
40+
}
41+
42+
@Suppress("TooGenericExceptionCaught")
43+
fun pushImage(frame: BufferedImage) {
44+
try {
45+
outputStream = socket.getOutputStream()
46+
img = frame
47+
var baos = ByteArrayOutputStream()
48+
ImageIO.write(img, "jpg", baos)
49+
val imageBytes = baos.toByteArray()
50+
outputStream.write(("Content-type: image/jpeg\r\n" +
51+
"Content-Length: " + imageBytes.size + "\r\n" +
52+
"\r\n").toByteArray())
53+
outputStream.write(imageBytes)
54+
outputStream.write(("\r\n--" + boundary + "\r\n").toByteArray())
55+
} catch (ex: Exception) {
56+
closes += 1
57+
println("exception $closes")
58+
socket = serverSocket.accept()
59+
writeHeader(socket.getOutputStream(), boundary)
60+
}
61+
}
62+
63+
override fun run() {
64+
try {
65+
println("go to http://localhost:8080 with browser")
66+
startStreamingServer()
67+
68+
while (true) {
69+
pushImage(toPush)
70+
}
71+
} catch (e: IOException) {
72+
return
73+
}
74+
}
75+
76+
fun stopStreamingServer() {
77+
socket.close()
78+
serverSocket.close()
79+
}
80+
}

src/test/kotlin/tests/PlaygroundTest.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ package tests
33
import org.junit.jupiter.api.Test
44
import org.junit.jupiter.api.Tag
55

6+
import javax.imageio.ImageIO
7+
68
import org.team5499.dashboard.Dashboard
79

810
@Tag("playground")
@@ -12,6 +14,10 @@ class PlaygroundTest {
1214
@Suppress("LongMethod")
1315
fun runServer() {
1416
println("starting server...")
17+
val img = ImageIO.read(this::class.java.getResource("/Calibration.001.jpeg"))
18+
val server = ImageServer(img)
19+
val imgThread = Thread(server)
20+
imgThread.start()
1521
println("go to http://localhost:5800/")
1622
Dashboard.start(this, "playgroundConf.json")
1723
Dashboard.setVariable("TEST", "testvalue")
168 KB
Loading

src/test/resources/playgroundConf.json

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,24 @@
1616
},
1717
"width": "12rem"
1818
},
19+
{
20+
"height": "300px",
21+
"id": "cameraFeed",
22+
"kwargs": {
23+
"imgSize": {
24+
"x": 960,
25+
"y": 540
26+
},
27+
"imgStart": {
28+
"x": 480,
29+
"y": 270
30+
},
31+
"src": "http://localhost:5801/"
32+
},
33+
"title": "Live Video Feed",
34+
"type": "CameraWidget",
35+
"width": "338px"
36+
},
1937
{
2038
"height": "auto",
2139
"id": "deekVarEditor",

0 commit comments

Comments
 (0)