Skip to content

Commit 40f386e

Browse files
committed
Merge branch 'develop' into candidate
2 parents 1d296f8 + fa9125c commit 40f386e

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+925
-170
lines changed

README.md

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,27 @@
11
# ConsoleVision
22
https://patbeagan.dev/projects/consolevideo
3+
![Screenshot_2022-04-27_23-45-04](https://user-images.githubusercontent.com/10187351/165678333-b3c45bbe-1a8b-49ae-91ab-48feb0337482.png)
34

45
# What is this project about?
56

7+
`ConsoleVision` is a library that will take a bitmap and convert it to a ANSI
8+
69
With this tool, you'll be able to display images without leaving your terminal. This can be useful when you are just sshing into another machine and don't have GUI access.
710

811
To make this more convenient, there is an option to run the tool as a server instead, so the only thing that you need installed is `curl`
912

1013
# What's included?
1114

12-
There are 2 deliverables from this repo
13-
- **lib** - a library that will take a bitmap and convert it to a ANSI
14-
- **app** - a wrapper that makes the library available via CLI and as a server.
15+
<details>
16+
<summary>As a Library</summary>
1517

16-
## As a library
1718

1819
The library jar file includes an implementation of [ANSI](https://mudhalla.net/tintin/info/ansicolor/) for the JVM. It has some similar content to [Jansi](http://fusesource.github.io/jansi/) (which I was unaware of at the time), but it includes extensions that make it more useful for image processing.
1920

20-
## As an app
21+
</details>
22+
23+
<details>
24+
<summary>As an App</summary>
2125

2226
The app supports a variety of command line flags which will allow for:
2327
- colorspace reduction
@@ -31,7 +35,12 @@ The app supports a variety of command line flags which will allow for:
3135
|-|-|-|-|
3236
|<img width="705" alt="Screen Shot 2022-02-05 at 8 51 15 AM" src="https://user-images.githubusercontent.com/10187351/152647110-105c8015-f7a7-4a98-aaff-6947722651b6.png">|<img width="700" alt="Screen Shot 2022-02-05 at 8 50 53 AM" src="https://user-images.githubusercontent.com/10187351/152647111-787eeef5-dd59-4ef8-8e0d-47678f44f953.png">|<img width="719" alt="Screen Shot 2022-02-05 at 9 03 37 AM" src="https://user-images.githubusercontent.com/10187351/152647252-c9035db3-a684-4818-8455-f917dade6700.png">|<img width="717" alt="Screen Shot 2022-02-05 at 9 02 59 AM" src="https://user-images.githubusercontent.com/10187351/152647253-1c6b5be0-bb7f-4b58-a98e-ba10c253106a.png">|
3337

34-
It also has the ability to be run as a server, with the `-s` flag. This will allow you to use a limited feature set of the command line tool, in a more convenient way.
38+
</details>
39+
40+
<details>
41+
<summary>As a Server</summary>
42+
43+
Running the tool as a server will allow you to use a limited feature set of the command line tool, in a more convenient way.
3544
- To upload a photo, POST to the `/upload` endpoint. You'll receive an image hash.
3645
- To retrieve a photo, GET to the `/im/{id}` endpoint, using an image hash.
3746
- To retrieve the last photo, GET to the `/last` endpoint
@@ -62,7 +71,10 @@ I have a server where this is deployed as well, if you just want to test it out.
6271
```bash
6372
curl 3.221.34.94/im/eefbb5b84ef2d8824f3fcaf64c54a63a
6473
```
74+
</details>
75+
76+
---
6577

66-
### Other things to note?
78+
## Other things to note?
6779

6880
Initial functionality that allowed for playing videos has been taken out. The video player that was being used, [humble](https://github.com/artclarke/humble-video/blob/master/humble-video-demos/src/main/java/io/humble/video/demos/DecodeAndPlayVideo.java), was not able to be packaged into a shadow jar.

app/build.gradle.kts

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,7 @@ repositories {
88
mavenCentral()
99
}
1010

11-
object Versions {
12-
const val versionKtor = "1.6.7"
13-
}
14-
1511
dependencies {
16-
implementation("ch.qos.logback:logback-classic:1.2.5")
17-
implementation("commons-codec:commons-codec:1.13")
18-
implementation("io.insert-koin:koin-core:3.1.2")
19-
implementation("io.ktor:ktor-html-builder:${Versions.versionKtor}")
20-
implementation("io.ktor:ktor-server-core:${Versions.versionKtor}")
21-
implementation("io.ktor:ktor-server-netty:${Versions.versionKtor}")
2212
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.0")
2313
implementation("org.mod4j.org.apache.commons:cli:1.0.0")
2414
implementation(project(":lib"))
Lines changed: 300 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,300 @@
1+
package dev.patbeagan.consolevision.consolevision.demo
2+
3+
import dev.patbeagan.consolevision.ansi.AnsiColor
4+
import dev.patbeagan.consolevision.ansi.AnsiConstants.HIDE_CURSOR
5+
6+
import dev.patbeagan.consolevision.ansi.AnsiConstants.RIS
7+
import dev.patbeagan.consolevision.style
8+
import dev.patbeagan.consolevision.types.ColorInt
9+
import dev.patbeagan.consolevision.types.CompressedPoint
10+
import dev.patbeagan.consolevision.types.CoordRect
11+
import dev.patbeagan.consolevision.types.List2D
12+
import dev.patbeagan.consolevision.types.coord
13+
import dev.patbeagan.consolevision.types.coordRect
14+
import kotlin.math.abs
15+
import kotlin.math.max
16+
import kotlin.math.roundToInt
17+
import kotlin.random.Random
18+
19+
fun main() {
20+
(1 coord 1).lineByDDATo(3 coord 4)
21+
getCircleByBresenham(1 coord 1, 3)
22+
}
23+
24+
fun CompressedPoint.lineByDDATo(end: CompressedPoint): List<CompressedPoint> {
25+
val dx = end.x - x
26+
val dy = end.y - y
27+
val steps = max(abs(dx), abs(dy))
28+
val stepX = dx / steps.toFloat();
29+
val stepY = dy / steps.toFloat();
30+
31+
var x = x.toFloat()
32+
var y = y.toFloat()
33+
34+
return (0..steps).map {
35+
val ret = x.roundToInt() coord y.roundToInt()
36+
x += stepX
37+
y += stepY
38+
ret
39+
}
40+
}
41+
42+
fun MutableList<CompressedPoint>.drawCircle(x: Int, y: Int, p: Int, q: Int) = addAll(
43+
listOf(
44+
(x + p) coord (y + q),
45+
(x - p) coord (y + q),
46+
(x + p) coord (y - q),
47+
(x - p) coord (y - q),
48+
(x + q) coord (y + p),
49+
(x - q) coord (y + p),
50+
(x + q) coord (y - p),
51+
(x - q) coord (y - p)
52+
)
53+
)
54+
55+
fun circleBres(xc: Int, yc: Int, r: Int): Pair<List<CompressedPoint>, CoordRect> {
56+
var x = 0
57+
var y = r
58+
var d = 3 - 2 * r
59+
val res = mutableListOf<CompressedPoint>()
60+
res.drawCircle(xc, yc, x, y)
61+
while (y >= x) {
62+
// for each pixel we will
63+
// draw all eight pixels
64+
x++
65+
66+
// check for decision parameter
67+
// and correspondingly
68+
// update d, x, y
69+
d = if (d > 0) {
70+
y--
71+
d + 4 * (x - y) + 10
72+
} else d + 4 * x + 6
73+
res.drawCircle(xc, yc, x, y)
74+
}
75+
val topLeft = (xc - r) coord (yc - r)
76+
val botRight = (xc + r) coord (yc + r)
77+
return res.distinct() to (topLeft coordRect botRight)
78+
}
79+
80+
fun List2D<Boolean>.fillPolygon(
81+
rect: CoordRect,
82+
fill: Boolean = true,
83+
): List2D<Boolean> {
84+
var last = false
85+
var isInShape = 0
86+
var entering = true
87+
88+
for (y in (rect.lesser.y + 1)..rect.greater.y) {
89+
for (x in rect.lesser.x..rect.greater.x) {
90+
if (y !in 1 until height) continue
91+
val b = this.at(x, y)
92+
when {
93+
b != last -> {
94+
if (isInShape == 2) entering = false
95+
if (isInShape == 0) entering = true
96+
if (entering) isInShape++ else isInShape--
97+
if (isInShape > 0) assign(x, y, fill)
98+
}
99+
100+
isInShape > 0 -> assign(x, y, fill)
101+
}
102+
last = b
103+
}
104+
105+
isInShape = 0
106+
entering = true
107+
last = false
108+
}
109+
return this
110+
}
111+
112+
fun getCircleByBresenham(center: CompressedPoint, radius: Int) {
113+
// var p = 0
114+
// var q = radius
115+
// var r = radius
116+
// var decision = 3 - 2 * r
117+
// val results = mutableListOf<Coord>()
118+
// while (p < q) {
119+
// results.drawCircle(center.x, center.y, p, q)
120+
// p++
121+
// if (decision < 0) {
122+
// decision += 4 * p + 6
123+
// } else {
124+
// r -= 1
125+
// decision += 4 * (p - q) + 10
126+
// }
127+
// results.drawCircle(center.x, center.y, p, q)
128+
// }
129+
// val sorted = results.sorted()
130+
// (0..results.maxBy { it.y }!!.y).forEach { y ->
131+
// (0..results.maxBy { it.x }!!.x).forEach { x ->
132+
// if (x coord y in sorted) print("x") else print(".")
133+
// }
134+
// println()
135+
// }
136+
// println(sorted)
137+
// println()
138+
139+
previewCircle()
140+
141+
val screen = (Array(40) {
142+
Array(80) { 0 }
143+
}).toList2D()
144+
var tick = 0
145+
forever(1000 / 20) {
146+
println(HIDE_CURSOR + RIS)
147+
(0..5).forEach {
148+
screen.addLayer((it * 192 % 256) shl 16 or 0x00AA88) {
149+
drawCircle(getRandomCircleCoordinate(it), 5)
150+
}
151+
}
152+
screen.addLayer(0xff0000) {
153+
drawCircle(((tick++) % 80) coord 20, 10)
154+
}
155+
screen.also { it.traverseMap { ColorInt(it) }.printScreenColor() }
156+
screen.traverseMutate { x, y, i -> 0 }
157+
}
158+
}
159+
160+
fun forever(limiter: Int? = 100, action: () -> Unit) {
161+
while (true) {
162+
action()
163+
limiter?.let { Thread.sleep(it.toLong()) }
164+
}
165+
}
166+
167+
168+
fun List2D<Boolean>.drawCircle(
169+
center: CompressedPoint,
170+
radius: Int
171+
) {
172+
circleBres(center.x, center.y, radius).also {
173+
val (list, _) = it
174+
traverseAssign(list, true)
175+
}
176+
}
177+
178+
fun List2D<Boolean>.drawLine(
179+
start: CompressedPoint,
180+
end: CompressedPoint
181+
) {
182+
start.lineByDDATo(end).also { traverseAssign(it, true) }
183+
}
184+
185+
fun List2D<Boolean>.drawCircleFilled(
186+
center: CompressedPoint,
187+
radius: Int
188+
) {
189+
circleBres(center.x, center.y, radius).also {
190+
val (list, rect) = it
191+
traverseAssign(list, true)
192+
this.fillPolygon(rect.modifyBy(gy = -1), true)
193+
}
194+
}
195+
196+
private fun previewCircle() {
197+
val screen = (Array(43) {
198+
Array(80) { false }
199+
}).toList2D()
200+
// circleBres(20, 20, 10).also { pair ->
201+
// val (list, rect) = pair
202+
// screen.traverseAssign(list, true)
203+
//
204+
// printScreen(screen)
205+
//
206+
// println()
207+
//
208+
// screen.fillPolygon(rect.modifyBy(gy = -1), true)
209+
// printScreen(screen)
210+
// }
211+
212+
screen.drawCircleFilled(30 coord 30, 10)
213+
screen.drawCircle(20 coord 20, 10)
214+
screen.printScreen()
215+
216+
val screen2 = screen.traverseMap { false }
217+
screen2.drawCircleFilled(35 coord 35, 10)
218+
219+
val merge =
220+
screen.mergeWith(screen2, false) { first: Boolean, second: Boolean -> first xor second }
221+
merge.printScreen()
222+
223+
val screenColor = screen
224+
.traverseMapIndexed { x, y, t -> x shl 16 or y shl 8 }
225+
.also {
226+
it.traverseMap { ColorInt(it) }.printScreenColor()
227+
}
228+
229+
val screenColor2 = screen
230+
.traverseMap { t -> if (t) 0xFF0000 else 0 }
231+
232+
val also = screenColor.mergeWith(screenColor2, 0) { first: Int, second: Int ->
233+
if (second != 0) second else first
234+
}.also {
235+
it.traverseMap { ColorInt(it) }.printScreenColor()
236+
}
237+
238+
val traverseMap = screen.traverseMap { false }
239+
traverseMap.drawLine(23 coord 20, 40 coord 75)
240+
(also to traverseMap).merge(0) { first, second ->
241+
if (second) 0xffffff else first
242+
}.also {
243+
it.traverseMap { ColorInt(it) }.printScreenColor()
244+
}
245+
246+
(0..5).forEach {
247+
also.addLayer((it * 192 % 256) shl 16 or 0x00AA88) {
248+
drawCircle(
249+
getRandomCircleCoordinate(it),
250+
5
251+
)
252+
}
253+
}
254+
also.also {
255+
it.traverseMap { ColorInt(it) }.printScreenColor()
256+
}
257+
258+
// circleBres(40, 30, 20).also {
259+
// val (list, rect) = it
260+
// screen.traverseAssign(list, true)
261+
// screen.fillPolygon(rect.modifyBy(gy = -1), true)
262+
// }
263+
//
264+
// printScreen(screen)
265+
}
266+
267+
private fun getRandomCircleCoordinate(it: Int) =
268+
it * 7 + (Random.nextInt() % 6) coord 32 + (Random.nextInt() % 20) * if (Random.nextBoolean()) 1 else -1
269+
270+
private fun List2D<Boolean>.printScreen() {
271+
traverseMap { t -> if (t) "XX" else ".." }.printAll("")
272+
}
273+
274+
private fun List2D<ColorInt>.printScreenColor() {
275+
traverseMap { t ->
276+
" ".style(
277+
colorBackground = AnsiColor.Custom(t)
278+
)
279+
}.printAll("")
280+
}
281+
282+
inline fun <reified T> Array<Array<T>>.toList2D() = List2D.from(map { rows -> rows.toList() })
283+
284+
inline fun <reified T, reified S, reified R> Pair<List2D<T>, List2D<S>>.merge(
285+
default: R,
286+
crossinline onElement: (first: T, second: S) -> R,
287+
): List2D<R> = first.mergeWith(second, default, onElement)
288+
289+
fun List2D<Int>.addLayer(
290+
color: Int,
291+
config: List2D<Boolean>.() -> Unit,
292+
) {
293+
val other = traverseMap { false }.also(config)
294+
this.traverseMutate { x, y, each ->
295+
if (other.isValidCoordinate(x coord y)) {
296+
if (other.at(x, y)) return@traverseMutate color
297+
}
298+
return@traverseMutate each
299+
}
300+
}

0 commit comments

Comments
 (0)