Skip to content

Commit 16b50b6

Browse files
committed
WIP
1 parent 0bb8f06 commit 16b50b6

4 files changed

Lines changed: 161 additions & 40 deletions

File tree

core/shared/src/main/scala/eu/joaocosta/minart/graphics/Color.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ package eu.joaocosta.minart.graphics
22

33
import scala.annotation.nowarn
44

5-
/** Representation of a RGB Color. */
5+
/** Representation of a RGBA Color. */
66
opaque type Color = Int
77

88
object Color {

core/shared/src/main/scala/eu/joaocosta/minart/graphics/Kernel.scala

Lines changed: 52 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -99,21 +99,44 @@ object Kernel {
9999
private val minY = -height / 2
100100

101101
/** Convolution function to use.
102-
* All color channels are handled the same way, with the alpha channel being forced to 255.
102+
* All color channels are handled the same way, with the alpha channel being forced to 255.
103103
*
104104
* Use as `plane.coflatMap(kernel)`
105105
*/
106+
/*def apply(getPixel: (Int, Int) => Color): Color = {
107+
var acc: LongColor = LongColor(constant, constant, constant, constant)
108+
109+
var dMY = 0
110+
while (dMY < height) {
111+
val dy = minY + dMY
112+
var dMX = 0
113+
val line = matrix(dMY)
114+
while (dMX < width) {
115+
val weight = line(dMX)
116+
if (weight != 0) {
117+
val dx = minX + dMX
118+
val color = getPixel(dx, dy)
119+
val c2 = LongColor.weight(LongColor(color), (weight * 255 / normalization).toByte)
120+
acc = LongColor.sumClamp(acc, c2)
121+
}
122+
dMX += 1
123+
}
124+
dMY += 1
125+
}
126+
acc.toColor*/
127+
106128
def apply(getPixel: (Int, Int) => Color): Color = {
107-
var accR: Int = 0
108-
var accG: Int = 0
109-
var accB: Int = 0
129+
var accR: Int = constant
130+
var accG: Int = constant
131+
var accB: Int = constant
110132

111133
var dMY = 0
112134
while (dMY < height) {
113-
val dy = minY + dMY
114-
var dMX = 0
135+
val dy = minY + dMY
136+
var dMX = 0
137+
val line = matrix(dMY)
115138
while (dMX < width) {
116-
val weight = matrix(dMY)(dMX)
139+
val weight = line(dMX)
117140
if (weight != 0) {
118141
val dx = minX + dMX
119142
val color = getPixel(dx, dy)
@@ -128,12 +151,11 @@ object Kernel {
128151

129152
if (normalization != 1)
130153
Color(
131-
constant + (accR / normalization),
132-
constant + (accG / normalization),
133-
constant + (accB / normalization)
154+
accR / normalization,
155+
accG / normalization,
156+
accB / normalization
134157
)
135-
else
136-
Color(constant + accR, constant + accG, constant + accB)
158+
else Color(accR, accG, accB)
137159
}
138160

139161
/** Creates a kernel equivalent to this one, but resized (keeping the center unchanged).
@@ -208,20 +230,24 @@ object Kernel {
208230
private val minY = -height / 2
209231

210232
def apply(getPixel: (Int, Int) => Color): Color = {
211-
var accR: Int = 0
212-
var accG: Int = 0
213-
var accB: Int = 0
214-
var accA: Int = 0
233+
var accR: Int = kernelR.constant
234+
var accG: Int = kernelG.constant
235+
var accB: Int = kernelB.constant
236+
var accA: Int = kernelA.constant
215237

216238
var dMY = 0
217239
while (dMY < height) {
218-
val dy = minY + dMY
219-
var dMX = 0
240+
val dy = minY + dMY
241+
var dMX = 0
242+
val lineR = matrixR(dMY)
243+
val lineG = matrixG(dMY)
244+
val lineB = matrixB(dMY)
245+
val lineA = matrixA(dMY)
220246
while (dMX < width) {
221-
val weightR = matrixR(dMY)(dMX)
222-
val weightG = matrixG(dMY)(dMX)
223-
val weightB = matrixB(dMY)(dMX)
224-
val weightA = matrixA(dMY)(dMX)
247+
val weightR = lineR(dMX)
248+
val weightG = lineG(dMX)
249+
val weightB = lineB(dMX)
250+
val weightA = lineA(dMX)
225251
if (weightR != 0 || weightG != 0 || weightB != 0 || weightA != 0) {
226252
val dx = minX + dMX
227253
val color = getPixel(dx, dy)
@@ -236,10 +262,10 @@ object Kernel {
236262
}
237263

238264
Color(
239-
kernelR.constant + (accR / kernelR.normalization),
240-
kernelG.constant + (accG / kernelG.normalization),
241-
kernelB.constant + (accB / kernelB.normalization),
242-
kernelA.constant + (accA / kernelA.normalization)
265+
accR / kernelR.normalization,
266+
accG / kernelG.normalization,
267+
accB / kernelB.normalization,
268+
accA / kernelA.normalization
243269
)
244270
}
245271
}
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
package eu.joaocosta.minart.graphics
2+
3+
import scala.annotation.nowarn
4+
5+
/** Representation of a RGBA Color optimized for mixing.
6+
* All operations:
7+
* - Have SWAR optimizations
8+
* - Require an explicit underflow/overflow behavior
9+
* - Handle all channels the same way
10+
*/
11+
12+
opaque type LongColor = Long
13+
14+
object LongColor {
15+
private final val mask: Long = 0x00ff_00ff_00ff_00ffL
16+
private final val aMask: Long = 0x00ff_0000_0000_0000L
17+
private final val rMask: Long = 0x0000_00ff_0000_0000L
18+
private final val gMask: Long = 0x0000_0000_00ff_0000L
19+
private final val bMask: Long = 0x0000_0000_0000_00ffL
20+
private final val aaMask: Long = 0xffff_0000_0000_0000L
21+
private final val rrMask: Long = 0x0000_ffff_0000_0000L
22+
private final val ggMask: Long = 0x0000_0000_ffff_0000L
23+
private final val bbMask: Long = 0x0000_0000_0000_ffffL
24+
private final val rgbMask: Long = 0x0000_00ff_00ff_00ffL
25+
private final val overflowMask: Long = 0x0100_0100_0100_0100L
26+
27+
extension (color: LongColor) {
28+
29+
/** The alpha channel value. */
30+
inline def a: Long = (color >> 48) & 0x000000ff
31+
32+
/** The red channel value. */
33+
inline def r: Long = (color >> 32) & 0x000000ff
34+
35+
/** The green channel value. */
36+
inline def g: Long = (color >> 16) & 0x000000ff
37+
38+
/** The green channel value. */
39+
inline def b: Long = (color & 0x000000ff)
40+
41+
inline def toColor: Color = Color(r.toInt, g.toInt, b.toInt, a.toInt)
42+
}
43+
44+
/** Sums two colors.
45+
* Values are clamped on overflow.
46+
*/
47+
inline def sumClamp(c1: LongColor, c2: LongColor): LongColor = {
48+
val res = c1 + c2
49+
val overflow = ((res & overflowMask) >> 8) * 255
50+
(res | overflow) & mask
51+
}
52+
53+
/** Sums two colors.
54+
* Values are wrapped around on overflow.
55+
*/
56+
inline def sumWrapAround(c1: LongColor, c2: LongColor): LongColor = {
57+
val res = (c1: Long) + (c2: Long)
58+
res & mask
59+
}
60+
61+
/** Multiplies all components by a weight from 0 to 255.
62+
* The behavior is undefined for values outside of that range.
63+
*/
64+
inline def weight(c: LongColor, w: Byte): LongColor = {
65+
val ww = java.lang.Byte.toUnsignedLong(w)
66+
val cc = c * ww
67+
(java.lang.Long.divideUnsigned(cc & aaMask, 255) & aMask) |
68+
(((cc & rrMask) / 255) & rMask) |
69+
(((cc & ggMask) / 255) & gMask) |
70+
(((cc & bbMask) / 255) & bMask)
71+
}
72+
73+
/** Creates a new color from RGB values (on the [0-255] range).
74+
* Overflow/Underflow will wrap around.
75+
*/
76+
def apply(r: Long, g: Long, b: Long): LongColor =
77+
(255L << 48) | ((r & 255) << 32) | ((g & 255) << 16) | (b & 255)
78+
79+
/** Creates a new color from RGBA values (on the [0-255] range).
80+
* Overflow/Underflow will wrap around.
81+
*/
82+
def apply(r: Long, g: Long, b: Long, a: Long): LongColor =
83+
(a << 48) | ((r & 255) << 32) | ((g & 255) << 16) | (b & 255)
84+
85+
def apply(color: Color): LongColor = LongColor(color.r, color.g, color.b, color.a)
86+
}

examples/snapshot/09-surface-views.md

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ We could use a simple function, but Minart already provides some optimized kerne
6464
out of the box.
6565

6666
```scala
67-
val blurKernel = Kernel.averageBlur(3, 3)
67+
val blurKernel = Kernel.averageBlur(9, 9)
6868

6969
```
7070

@@ -77,33 +77,42 @@ def application(t: Double, canvas: Canvas): Unit = {
7777
val zoom = 1.0 / (frameSin + 2.0)
7878

7979
val image = updatedBitmap.view.repeating // Create an infinite Plane from our surface
80-
.scale(zoom, zoom) // Scale
80+
.toSurfaceView(canvasSettings.width, canvasSettings.height)
81+
.precompute
8182
.coflatMap(blurKernel) // Average blur
82-
.rotate(t) // Rotate
83-
.contramap((x, y) => (x + (5 * Math.sin(t + y / 10.0)).toInt, y)) // Wobbly effect
84-
.flatMap(color =>
85-
(x, y) => // Checkerboard effect
86-
if (x % 32 < 16 != y % 32 < 16) color.invert
87-
else color
88-
)
89-
90-
canvas.blitPlane(image)(0, 0)
83+
84+
canvas.blit(image)(0, 0)
9185
}
9286
```
9387

9488
### Putting it all together
9589

9690
```scala
97-
val canvasSettings = Canvas.Settings(width = 128, height = 128, scale = Some(4), clearColor = Color(0, 0, 0))
91+
val frameCounter = {
92+
var frameNumber: Int = 0
93+
var timer = System.currentTimeMillis
94+
() => {
95+
frameNumber += 1
96+
if (frameNumber % 10 == 0) {
97+
val currTime = System.currentTimeMillis()
98+
val fps = 10.0 / ((currTime - timer) / 1000.0)
99+
println("FPS:" + fps)
100+
timer = System.currentTimeMillis()
101+
}
102+
}
103+
}
104+
105+
val canvasSettings = Canvas.Settings(width = 512, height = 512, scale = Some(1), clearColor = Color(0, 0, 0))
98106

99107
AppLoop
100108
.statefulRenderLoop((t: Double) => (canvas: Canvas) => {
109+
frameCounter()
101110
canvas.clear()
102111
application(t, canvas)
103112
canvas.redraw()
104113
t + 0.01
105114
}
106115
)
107-
.configure(canvasSettings, LoopFrequency.hz60, 0)
116+
.configure(canvasSettings, LoopFrequency.Uncapped, 0)
108117
.run()
109118
```

0 commit comments

Comments
 (0)