diff --git a/orx-boofcv/build.gradle b/orx-boofcv/build.gradle new file mode 100644 index 00000000..74819b97 --- /dev/null +++ b/orx-boofcv/build.gradle @@ -0,0 +1,22 @@ +sourceSets { + demo { + java { + srcDirs = ["src/demo/kotlin"] + compileClasspath += main.getCompileClasspath() + runtimeClasspath += main.getRuntimeClasspath() + } + } +} + +def boofcvVersion = "0.34" + +dependencies { + + compile("org.boofcv:boofcv-core:$boofcvVersion") + + demoImplementation("org.openrndr:openrndr-core:$openrndrVersion") + demoImplementation("org.openrndr:openrndr-extensions:$openrndrVersion") + demoRuntimeOnly("org.openrndr:openrndr-gl3:$openrndrVersion") + demoRuntimeOnly("org.openrndr:openrndr-gl3-natives-$openrndrOS:$openrndrVersion") + demoImplementation(sourceSets.getByName("main").output) +} \ No newline at end of file diff --git a/orx-boofcv/src/main/kotlin/Binding.kt b/orx-boofcv/src/main/kotlin/Binding.kt new file mode 100644 index 00000000..c2f5159b --- /dev/null +++ b/orx-boofcv/src/main/kotlin/Binding.kt @@ -0,0 +1,187 @@ +package org.openrndr.boofcv.binding + +import boofcv.struct.image.GrayF32 +import boofcv.struct.image.GrayF64 +import boofcv.struct.image.GrayU8 +import boofcv.struct.image.Planar +import org.openrndr.color.ColorRGBa +import org.openrndr.draw.ColorBuffer +import org.openrndr.draw.ColorFormat +import org.openrndr.draw.ColorType +import org.openrndr.draw.colorBuffer +import kotlin.experimental.and + +fun ColorBuffer.toGrayF32() : GrayF32 { + val p = GrayF32(width, height) + shadow.download() + + var offset = 0 + for (y in 0 until height) { + for (x in 0 until width) { + val c = shadow.read(x, y) + p.data[offset] = (c.r * 255).toFloat() + offset++ + } + } + return p +} + +fun ColorBuffer.toGrayF64() : GrayF64 { + val p = GrayF64(width, height) + shadow.download() + + var offset = 0 + for (y in 0 until height) { + for (x in 0 until width) { + val c = shadow.read(x, y) + p.data[offset] = (c.r * 255) + offset++ + } + } + return p +} + +fun ColorBuffer.toPlanarF32() : Planar { + val p = Planar(GrayF32::class.java, width, height, format.componentCount) + shadow.download() + + val bands = p.bands + + var offset = 0 + for (y in 0 until height) { + for (x in 0 until width) { + val c = shadow.read(x, y) + bands[0].data[offset] = (c.r * 255).toFloat() + bands[1].data[offset] = (c.g * 255).toFloat() + bands[2].data[offset] = (c.b * 255).toFloat() + offset++ + } + } + return p +} + +fun ColorBuffer.toPlanarU8() : Planar { + val p = Planar(GrayU8::class.java, width, height, format.componentCount) + shadow.download() + + val bands = p.bands + + var offset = 0 + for (y in 0 until height) { + for (x in 0 until width) { + val c = shadow.read(x, y) + bands[0].data[offset] = (c.r * 255).toByte() + bands[1].data[offset] = (c.g * 255).toByte() + bands[2].data[offset] = (c.b * 255).toByte() + offset++ + } + } + return p +} + +fun ColorBuffer.toGrayU8() : GrayU8 { + val p = GrayU8(width, height) + shadow.download() + + var offset = 0 + for (y in 0 until height) { + for (x in 0 until width) { + val c = shadow.read(x, y) + p.data[offset] = (c.r * 255).toInt().coerceIn(0, 255).toByte() + offset++ + } + } + return p +} + + +fun GrayU8.toColorBuffer() : ColorBuffer { + val cb = colorBuffer(width, height, 1.0, ColorFormat.RGB, ColorType.UINT8) + val shadow = cb.shadow + shadow.buffer.rewind() + var offset = 0 + for (y in 0 until height) { + for (x in 0 until width) { + val r = (data[offset].toInt() and 0xff).toDouble() / 255.0 + offset++ + shadow.write(x, y, ColorRGBa(r, r, r, 1.0)) + } + } + shadow.upload() + return cb +} + +fun GrayF32.toColorBuffer() : ColorBuffer { + val cb = colorBuffer(width, height, 1.0, ColorFormat.RGB, ColorType.FLOAT32) + val shadow = cb.shadow + shadow.buffer.rewind() + var offset = 0 + for (y in 0 until height) { + for (x in 0 until width) { + val r = data[offset].toDouble() / 255.0 + offset++ + shadow.write(x, y, ColorRGBa(r, r, r)) + } + } + shadow.upload() + return cb +} + +fun Planar.toColorBuffer() : ColorBuffer { + val bandCount = bands.size + val format = when (bandCount) { + 1 -> ColorFormat.R + 2 -> ColorFormat.RG + 3 -> ColorFormat.RGB + 4 -> ColorFormat.RGBa + else -> throw IllegalArgumentException("only 1 to 4 bands supported") + } + + val bands = bands + val cb = colorBuffer(width, height, 1.0, format, ColorType.UINT8) + val shadow = cb.shadow + shadow.buffer.rewind() + var offset = 0 + for (y in 0 until height) { + for (x in 0 until width) { + val r = (bands[0].data[offset].toInt() and 0xff).toDouble() / 255.0 + val g = if (bandCount >= 2) (bands[1].data[offset].toInt() and 0xff).toDouble() / 255.0 else 0.0 + val b = if (bandCount >= 3) (bands[2].data[offset].toInt() and 0xff).toDouble() / 255.0 else 0.0 + val a = if (bandCount >= 4) (bands[2].data[offset].toInt() and 0xff).toDouble() / 255.0 else 1.0 + offset++ + shadow.write(x, y, ColorRGBa(r, g, b, a)) + } + } + shadow.upload() + return cb +} + +@JvmName("grayF32ToColorBuffer") +fun Planar.toColorBuffer() : ColorBuffer { + val bandCount = bands.size + val format = when (bandCount) { + 1 -> ColorFormat.R + 2 -> ColorFormat.RG + 3 -> ColorFormat.RGB + 4 -> ColorFormat.RGBa + else -> throw IllegalArgumentException("only 1 to 4 bands supported") + } + + val bands = bands + val cb = colorBuffer(width, height, 1.0, format, ColorType.UINT8) + val shadow = cb.shadow + shadow.buffer.rewind() + var offset = 0 + for (y in 0 until height) { + for (x in 0 until width) { + val r = bands[0].data[offset] / 255.0 + val g = if (bandCount >= 2) bands[1].data[offset] / 255.0 else 0.0 + val b = if (bandCount >= 3) bands[2].data[offset] / 255.0 else 0.0 + val a = if (bandCount >= 4) bands[3].data[offset] / 255.0 else 1.0 + offset++ + shadow.write(x, y, ColorRGBa(r, g, b, a)) + } + } + shadow.upload() + return cb +} \ No newline at end of file diff --git a/orx-boofcv/src/main/kotlin/ContourConversion.kt b/orx-boofcv/src/main/kotlin/ContourConversion.kt new file mode 100644 index 00000000..b04c1c77 --- /dev/null +++ b/orx-boofcv/src/main/kotlin/ContourConversion.kt @@ -0,0 +1,22 @@ +package org.openrndr.boofcv.binding + +import boofcv.alg.filter.binary.Contour +import org.openrndr.shape.Shape +import org.openrndr.shape.ShapeContour + +fun Contour.toShape(): Shape { + val external = external.toVector2s() + val internal = internal.filter { it.size > 0 }. map { it.toVector2s() } + val contours = listOf(ShapeContour.fromPoints(external, false)) + + internal.map { + ShapeContour.fromPoints(it, false) + } + + return Shape(contours) +} + +fun List.toShapes(): List { + return this.filter { it.external.isNotEmpty() }.map { + it.toShape() + } +} \ No newline at end of file diff --git a/orx-boofcv/src/main/kotlin/Drawing.kt b/orx-boofcv/src/main/kotlin/Drawing.kt new file mode 100644 index 00000000..06df7654 --- /dev/null +++ b/orx-boofcv/src/main/kotlin/Drawing.kt @@ -0,0 +1,83 @@ +package org.openrndr.boofcv.binding + +import georegression.struct.line.LineSegment2D_F32 +import georegression.struct.line.LineSegment2D_F64 +import georegression.struct.trig.Circle2D_F32 +import georegression.struct.trig.Circle2D_F64 +import org.openrndr.draw.Drawer +import org.openrndr.math.Vector2 +import org.openrndr.shape.Circle + +fun Drawer.lineSegment(segment: LineSegment2D_F32) { + lineSegment( + segment.a.x.toDouble(), + segment.a.y.toDouble(), + segment.b.x.toDouble(), + segment.b.y.toDouble() + ) +} + +@JvmName("lineSegments2D_F32") +fun Drawer.lineSegments(segments: List) { + lineSegments( + segments.flatMap { segment -> + listOf( + Vector2(segment.a.x.toDouble(), segment.a.y.toDouble()), + Vector2(segment.b.x.toDouble(), segment.b.y.toDouble()) + ) + } + ) +} + +fun Drawer.lineSegment(segment: LineSegment2D_F64) { + lineSegment( + segment.a.x, + segment.a.y, + segment.b.x, + segment.b.y + ) +} + +@JvmName("lineSegments2D_F64") +fun Drawer.lineSegments(segments: List) { + lineSegments( + segments.flatMap { segment -> + listOf( + Vector2(segment.a.x, segment.a.y), + Vector2(segment.b.x, segment.b.y) + ) + } + ) +} + +fun Drawer.circle(circle: Circle2D_F32) { + circle( + circle.center.x.toDouble(), circle.center.y.toDouble(), + circle.radius.toDouble() + ) +} + +fun Drawer.circle(circle: Circle2D_F64) { + circle( + circle.center.x, circle.center.y, + circle.radius + ) +} + +@JvmName("circles2D_F32") +fun Drawer.circles(circles: List) { + circles( + circles.map { + Circle(it.center.x.toDouble(), it.center.y.toDouble(), it.radius.toDouble()) + } + ) +} + +@JvmName("circles2D_F64") +fun Drawer.circles(circles: List) { + circles( + circles.map { + Circle(it.center.x.toDouble(), it.center.y.toDouble(), it.radius.toDouble()) + } + ) +} \ No newline at end of file diff --git a/orx-boofcv/src/main/kotlin/ImageFlowConversion.kt b/orx-boofcv/src/main/kotlin/ImageFlowConversion.kt new file mode 100644 index 00000000..2e6f63d6 --- /dev/null +++ b/orx-boofcv/src/main/kotlin/ImageFlowConversion.kt @@ -0,0 +1,33 @@ +package org.openrndr.boofcv.binding + +import boofcv.struct.flow.ImageFlow +import org.openrndr.draw.ColorBuffer +import org.openrndr.draw.ColorFormat +import org.openrndr.draw.ColorType +import org.openrndr.draw.colorBuffer +import java.nio.Buffer +import java.nio.ByteBuffer +import java.nio.ByteOrder + +fun ImageFlow.toColorBuffer(): ColorBuffer { + + val cb = colorBuffer( + width, height, format = ColorFormat.RG, + type = ColorType.FLOAT32 + ) + + val bb = ByteBuffer.allocateDirect(width * height * 8) + bb.order(ByteOrder.nativeOrder()) + for (y in 0 until height) { + for (x in 0 until width) { + val f = get(x, y) + bb.putFloat(f.x) + bb.putFloat(f.y) + } + } + + (bb as Buffer).rewind() + cb.write(bb) + cb.flipV = true + return cb +} \ No newline at end of file diff --git a/orx-boofcv/src/main/kotlin/PointConversion.kt b/orx-boofcv/src/main/kotlin/PointConversion.kt new file mode 100644 index 00000000..7a8d06b9 --- /dev/null +++ b/orx-boofcv/src/main/kotlin/PointConversion.kt @@ -0,0 +1,11 @@ +package org.openrndr.boofcv.binding + +import georegression.struct.point.Point2D_F32 +import georegression.struct.point.Point2D_F64 +import georegression.struct.point.Point2D_I32 +import org.openrndr.math.Vector2 + +fun Point2D_I32.toVector2() = Vector2(x.toDouble(), y.toDouble()) +fun Point2D_F32.toVector2() = Vector2(x.toDouble(), y.toDouble()) +fun Point2D_F64.toVector2() = Vector2(x.toDouble(), y.toDouble()) +fun List.toVector2s() = this.map { it.toVector2() } diff --git a/settings.gradle b/settings.gradle index 34c08dfe..141bb5ac 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,6 +1,7 @@ rootProject.name = 'orx' -include 'orx-camera', +include 'orx-boofcv', + 'orx-camera', 'orx-compositor', 'orx-easing', 'orx-file-watcher',