Compare commits
No commits in common. "4e08680b22e8c8ac4c958e516b8797035bf15092" and "e3b5ad0df42c7251ba521ddf6b79d586da6745ad" have entirely different histories.
4e08680b22
...
e3b5ad0df4
@ -4,11 +4,9 @@
|
|||||||
|
|
||||||
### Added
|
### Added
|
||||||
- `alpha` extension for feature attribute builder
|
- `alpha` extension for feature attribute builder
|
||||||
- PNG export
|
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
- avoid drawing features with VisibleAttribute false
|
- avoid drawing features with VisibleAttribute false
|
||||||
- Move SVG export to `features` and make it usable for maps as well
|
|
||||||
|
|
||||||
### Deprecated
|
### Deprecated
|
||||||
|
|
||||||
|
@ -9,7 +9,7 @@ val kmathVersion: String by extra("0.4.0")
|
|||||||
|
|
||||||
allprojects {
|
allprojects {
|
||||||
group = "space.kscience"
|
group = "space.kscience"
|
||||||
version = "0.4.0-dev-3"
|
version = "0.4.0-dev-2"
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
mavenLocal()
|
mavenLocal()
|
||||||
|
@ -4,20 +4,22 @@ import androidx.compose.foundation.ContextMenuArea
|
|||||||
import androidx.compose.foundation.ContextMenuItem
|
import androidx.compose.foundation.ContextMenuItem
|
||||||
import androidx.compose.material.MaterialTheme
|
import androidx.compose.material.MaterialTheme
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.ui.geometry.Size
|
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.text.rememberTextMeasurer
|
|
||||||
import androidx.compose.ui.window.Window
|
import androidx.compose.ui.window.Window
|
||||||
import androidx.compose.ui.window.application
|
import androidx.compose.ui.window.application
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.isActive
|
import kotlinx.coroutines.isActive
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import space.kscience.kmath.geometry.Angle
|
import space.kscience.kmath.geometry.Angle
|
||||||
import space.kscience.maps.features.*
|
import space.kscience.maps.features.FeatureStore
|
||||||
|
import space.kscience.maps.features.ViewConfig
|
||||||
|
import space.kscience.maps.features.ViewPoint
|
||||||
|
import space.kscience.maps.features.color
|
||||||
import space.kscience.maps.scheme.*
|
import space.kscience.maps.scheme.*
|
||||||
import space.kscience.maps.svg.exportToPng
|
import space.kscience.maps.svg.FeatureStateSnapshot
|
||||||
import space.kscience.maps.svg.exportToSvg
|
import space.kscience.maps.svg.exportToSvg
|
||||||
|
import space.kscience.maps.svg.snapshot
|
||||||
import java.awt.Desktop
|
import java.awt.Desktop
|
||||||
import java.nio.file.Files
|
import java.nio.file.Files
|
||||||
|
|
||||||
@ -56,31 +58,23 @@ fun App() {
|
|||||||
|
|
||||||
var viewPoint: ViewPoint<XY> by remember { mutableStateOf(initialViewPoint) }
|
var viewPoint: ViewPoint<XY> by remember { mutableStateOf(initialViewPoint) }
|
||||||
|
|
||||||
val painterCache = features.pointerCache()
|
var snapshot: FeatureStateSnapshot<XY>? by remember { mutableStateOf(null) }
|
||||||
|
|
||||||
val textMeasurer = rememberTextMeasurer()
|
if (snapshot == null) {
|
||||||
|
snapshot = features.snapshot()
|
||||||
|
}
|
||||||
|
|
||||||
ContextMenuArea(
|
ContextMenuArea(
|
||||||
items = {
|
items = {
|
||||||
listOf(
|
listOf(
|
||||||
ContextMenuItem("Export to SVG") {
|
ContextMenuItem("Export to SVG") {
|
||||||
|
snapshot?.let {
|
||||||
val path = Files.createTempFile("scheme-kt-", ".svg")
|
val path = Files.createTempFile("scheme-kt-", ".svg")
|
||||||
features.exportToSvg(viewPoint, painterCache, Size(800f, 800f), path)
|
it.exportToSvg(viewPoint, 800.0, 800.0, path)
|
||||||
println(path.toFile())
|
|
||||||
Desktop.getDesktop().browse(path.toFile().toURI())
|
|
||||||
},
|
|
||||||
ContextMenuItem("Export to PNG") {
|
|
||||||
val path = Files.createTempFile("scheme-kt-", ".png")
|
|
||||||
features.exportToPng(
|
|
||||||
viewPoint,
|
|
||||||
painterCache,
|
|
||||||
textMeasurer,
|
|
||||||
Size(800f, 800f),
|
|
||||||
path
|
|
||||||
)
|
|
||||||
println(path.toFile())
|
println(path.toFile())
|
||||||
Desktop.getDesktop().browse(path.toFile().toURI())
|
Desktop.getDesktop().browse(path.toFile().toURI())
|
||||||
}
|
}
|
||||||
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
|
@ -15,7 +15,7 @@ import space.kscience.maps.features.*
|
|||||||
import kotlin.math.*
|
import kotlin.math.*
|
||||||
|
|
||||||
|
|
||||||
public class MapCanvasState internal constructor(
|
public class MapCanvasState private constructor(
|
||||||
public val mapTileProvider: MapTileProvider,
|
public val mapTileProvider: MapTileProvider,
|
||||||
config: ViewConfig<Gmc>,
|
config: ViewConfig<Gmc>,
|
||||||
) : CanvasState<Gmc>(config) {
|
) : CanvasState<Gmc>(config) {
|
||||||
|
@ -1,55 +0,0 @@
|
|||||||
package space.kscience.maps.compose
|
|
||||||
|
|
||||||
import androidx.compose.ui.geometry.Size
|
|
||||||
import androidx.compose.ui.graphics.asSkiaBitmap
|
|
||||||
import androidx.compose.ui.graphics.painter.Painter
|
|
||||||
import androidx.compose.ui.text.TextMeasurer
|
|
||||||
import androidx.compose.ui.unit.DpSize
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import org.jetbrains.skia.Image
|
|
||||||
import org.jfree.svg.SVGUtils
|
|
||||||
import space.kscience.maps.coordinates.Gmc
|
|
||||||
import space.kscience.maps.features.FeatureSet
|
|
||||||
import space.kscience.maps.features.PainterFeature
|
|
||||||
import space.kscience.maps.features.ViewConfig
|
|
||||||
import space.kscience.maps.features.ViewPoint
|
|
||||||
import space.kscience.maps.svg.generateBitmap
|
|
||||||
import space.kscience.maps.svg.generateSvg
|
|
||||||
import java.nio.file.Path
|
|
||||||
import kotlin.io.path.writeBytes
|
|
||||||
|
|
||||||
public fun FeatureSet<Gmc>.exportToSvg(
|
|
||||||
mapTileProvider: MapTileProvider,
|
|
||||||
viewPoint: ViewPoint<Gmc>,
|
|
||||||
painterCache: Map<PainterFeature<Gmc>, Painter>,
|
|
||||||
size: Size,
|
|
||||||
path: Path,
|
|
||||||
) {
|
|
||||||
val mapCanvasState: MapCanvasState = MapCanvasState(mapTileProvider, ViewConfig()).apply {
|
|
||||||
this.viewPoint = viewPoint
|
|
||||||
this.canvasSize = DpSize(size.width.dp, size.height.dp)
|
|
||||||
}
|
|
||||||
|
|
||||||
val svgString: String = generateSvg(mapCanvasState, painterCache)
|
|
||||||
SVGUtils.writeToSVG(path.toFile(), svgString)
|
|
||||||
}
|
|
||||||
|
|
||||||
public fun FeatureSet<Gmc>.exportToPng(
|
|
||||||
mapTileProvider: MapTileProvider,
|
|
||||||
viewPoint: ViewPoint<Gmc>,
|
|
||||||
painterCache: Map<PainterFeature<Gmc>, Painter>,
|
|
||||||
textMeasurer: TextMeasurer,
|
|
||||||
size: Size,
|
|
||||||
path: Path,
|
|
||||||
) {
|
|
||||||
val mapCanvasState: MapCanvasState = MapCanvasState(mapTileProvider, ViewConfig()).apply {
|
|
||||||
this.viewPoint = viewPoint
|
|
||||||
this.canvasSize = DpSize(size.width.dp, size.height.dp)
|
|
||||||
}
|
|
||||||
|
|
||||||
val bitmap = generateBitmap(mapCanvasState, painterCache, textMeasurer, size)
|
|
||||||
|
|
||||||
Image.makeFromBitmap(bitmap.asSkiaBitmap()).encodeToData()?.bytes?.let {
|
|
||||||
path.writeBytes(it)
|
|
||||||
}
|
|
||||||
}
|
|
@ -37,8 +37,4 @@ kscience {
|
|||||||
api("io.github.oshai:kotlin-logging:6.0.3")
|
api("io.github.oshai:kotlin-logging:6.0.3")
|
||||||
api("com.benasher44:uuid:0.8.4")
|
api("com.benasher44:uuid:0.8.4")
|
||||||
}
|
}
|
||||||
|
|
||||||
jvmMain{
|
|
||||||
api("org.jfree:org.jfree.svg:5.0.4")
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -4,7 +4,6 @@ import androidx.compose.foundation.Canvas
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.key
|
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.geometry.Offset
|
import androidx.compose.ui.geometry.Offset
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
@ -76,11 +75,6 @@ public class ComposeFeatureDrawScope<T : Any>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
|
||||||
public fun <T: Any> FeatureSet<T>.pointerCache(): Map<PainterFeature<T>, Painter> = key(features) {
|
|
||||||
features.values.filterIsInstance<PainterFeature<T>>().associateWith { it.getPainter() }
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a canvas with extended functionality (e.g., drawing text)
|
* Create a canvas with extended functionality (e.g., drawing text)
|
||||||
@ -96,7 +90,7 @@ public fun <T : Any> FeatureCanvas(
|
|||||||
) {
|
) {
|
||||||
val textMeasurer = rememberTextMeasurer(0)
|
val textMeasurer = rememberTextMeasurer(0)
|
||||||
|
|
||||||
val features: Map<String, Feature<T>> by featureFlow.sample(sampleDuration).collectAsState(featureFlow.value)
|
val features by featureFlow.sample(sampleDuration).collectAsState(featureFlow.value)
|
||||||
|
|
||||||
val painterCache = features.values
|
val painterCache = features.values
|
||||||
.filterIsInstance<PainterFeature<T>>()
|
.filterIsInstance<PainterFeature<T>>()
|
||||||
|
@ -1,34 +0,0 @@
|
|||||||
package space.kscience.maps.svg
|
|
||||||
|
|
||||||
import androidx.compose.ui.geometry.Size
|
|
||||||
import androidx.compose.ui.graphics.Canvas
|
|
||||||
import androidx.compose.ui.graphics.ImageBitmap
|
|
||||||
import androidx.compose.ui.graphics.drawscope.CanvasDrawScope
|
|
||||||
import androidx.compose.ui.graphics.painter.Painter
|
|
||||||
import androidx.compose.ui.text.TextMeasurer
|
|
||||||
import androidx.compose.ui.unit.Density
|
|
||||||
import androidx.compose.ui.unit.LayoutDirection
|
|
||||||
import space.kscience.maps.features.CanvasState
|
|
||||||
import space.kscience.maps.features.ComposeFeatureDrawScope
|
|
||||||
import space.kscience.maps.features.FeatureSet
|
|
||||||
import space.kscience.maps.features.PainterFeature
|
|
||||||
|
|
||||||
public fun <T : Any> FeatureSet<T>.generateBitmap(
|
|
||||||
canvasState: CanvasState<T>,
|
|
||||||
painterCache: Map<PainterFeature<T>, Painter>,
|
|
||||||
textMeasurer: TextMeasurer,
|
|
||||||
size: Size
|
|
||||||
): ImageBitmap {
|
|
||||||
|
|
||||||
val bitmap = ImageBitmap(size.width.toInt(), size.height.toInt())
|
|
||||||
|
|
||||||
CanvasDrawScope().draw(
|
|
||||||
density = Density(1f),
|
|
||||||
layoutDirection = LayoutDirection.Ltr,
|
|
||||||
canvas = Canvas(bitmap),
|
|
||||||
size = size,
|
|
||||||
) {
|
|
||||||
ComposeFeatureDrawScope(this, canvasState, painterCache, textMeasurer).features(this@generateBitmap)
|
|
||||||
}
|
|
||||||
return bitmap
|
|
||||||
}
|
|
@ -1,45 +0,0 @@
|
|||||||
package space.kscience.maps.svg
|
|
||||||
|
|
||||||
import androidx.compose.ui.graphics.painter.Painter
|
|
||||||
import org.jfree.svg.SVGGraphics2D
|
|
||||||
import space.kscience.attributes.Attributes
|
|
||||||
import space.kscience.attributes.plus
|
|
||||||
import space.kscience.maps.features.*
|
|
||||||
|
|
||||||
|
|
||||||
public fun <T : Any> FeatureDrawScope<T>.features(featureSet: FeatureSet<T>) {
|
|
||||||
featureSet.features.entries.sortedBy { it.value.z }
|
|
||||||
.filter { state.viewPoint.zoom in it.value.zoomRange }
|
|
||||||
.forEach { (id, feature) ->
|
|
||||||
val attributesCache = mutableMapOf<List<String>, Attributes>()
|
|
||||||
|
|
||||||
fun computeGroupAttributes(path: List<String>): Attributes = attributesCache.getOrPut(path) {
|
|
||||||
if (path.isEmpty()) return Attributes.EMPTY
|
|
||||||
else if (path.size == 1) {
|
|
||||||
featureSet.features[path.first()]?.attributes ?: Attributes.EMPTY
|
|
||||||
} else {
|
|
||||||
computeGroupAttributes(path.dropLast(1)) +
|
|
||||||
(featureSet.features[path.first()]?.attributes ?: Attributes.EMPTY)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val path = id.split("/")
|
|
||||||
drawFeature(feature, computeGroupAttributes(path.dropLast(1)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public fun <T : Any> FeatureSet<T>.generateSvg(
|
|
||||||
canvasState: CanvasState<T>,
|
|
||||||
painterCache: Map<PainterFeature<T>, Painter>,
|
|
||||||
id: String? = null,
|
|
||||||
): String {
|
|
||||||
val svgGraphics2D: SVGGraphics2D = SVGGraphics2D(
|
|
||||||
canvasState.canvasSize.width.value.toDouble(),
|
|
||||||
canvasState.canvasSize.height.value.toDouble()
|
|
||||||
)
|
|
||||||
val svgScope = SvgDrawScope(canvasState, svgGraphics2D, painterCache)
|
|
||||||
|
|
||||||
svgScope.features(this)
|
|
||||||
|
|
||||||
return svgGraphics2D.getSVGElement(id)
|
|
||||||
}
|
|
@ -14,6 +14,7 @@ kscience {
|
|||||||
api(projects.mapsKtFeatures)
|
api(projects.mapsKtFeatures)
|
||||||
}
|
}
|
||||||
jvmMain{
|
jvmMain{
|
||||||
|
implementation("org.jfree:org.jfree.svg:5.0.4")
|
||||||
api(compose.desktop.currentOs)
|
api(compose.desktop.currentOs)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,7 @@ import androidx.compose.ui.unit.dp
|
|||||||
import space.kscience.maps.features.*
|
import space.kscience.maps.features.*
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
|
|
||||||
public class XYCanvasState internal constructor(
|
public class XYCanvasState(
|
||||||
config: ViewConfig<XY>,
|
config: ViewConfig<XY>,
|
||||||
) : CanvasState<XY>(config) {
|
) : CanvasState<XY>(config) {
|
||||||
override val space: CoordinateSpace<XY>
|
override val space: CoordinateSpace<XY>
|
||||||
|
@ -14,21 +14,19 @@ import androidx.compose.ui.unit.IntSize
|
|||||||
import androidx.compose.ui.unit.LayoutDirection
|
import androidx.compose.ui.unit.LayoutDirection
|
||||||
import org.jfree.svg.SVGGraphics2D
|
import org.jfree.svg.SVGGraphics2D
|
||||||
import space.kscience.attributes.Attributes
|
import space.kscience.attributes.Attributes
|
||||||
import space.kscience.maps.features.CanvasState
|
import space.kscience.maps.features.*
|
||||||
import space.kscience.maps.features.ColorAttribute
|
import space.kscience.maps.scheme.XY
|
||||||
import space.kscience.maps.features.FeatureDrawScope
|
|
||||||
import space.kscience.maps.features.PainterFeature
|
|
||||||
import java.awt.BasicStroke
|
import java.awt.BasicStroke
|
||||||
import java.awt.geom.*
|
import java.awt.geom.*
|
||||||
import java.awt.image.AffineTransformOp
|
import java.awt.image.AffineTransformOp
|
||||||
import java.awt.Color as AWTColor
|
import java.awt.Color as AWTColor
|
||||||
|
|
||||||
public class SvgDrawScope<T: Any>(
|
public class SvgDrawScope(
|
||||||
state: CanvasState<T>,
|
state: CanvasState<XY>,
|
||||||
private val graphics: SVGGraphics2D,
|
private val graphics: SVGGraphics2D,
|
||||||
private val painterCache: Map<PainterFeature<T>, Painter>,
|
private val painterCache: Map<PainterFeature<XY>, Painter>,
|
||||||
private val defaultStrokeWidth: Float = 1f,
|
private val defaultStrokeWidth: Float = 1f,
|
||||||
) : FeatureDrawScope<T>(state) {
|
) : FeatureDrawScope<XY>(state) {
|
||||||
|
|
||||||
override val layoutDirection: LayoutDirection
|
override val layoutDirection: LayoutDirection
|
||||||
get() = LayoutDirection.Ltr
|
get() = LayoutDirection.Ltr
|
||||||
@ -468,14 +466,14 @@ public class SvgDrawScope<T: Any>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// public fun renderText(
|
public fun renderText(
|
||||||
// textFeature: TextFeature<T>,
|
textFeature: TextFeature<XY>,
|
||||||
// ) {
|
) {
|
||||||
// textFeature.color?.let { setupColor(it) }
|
textFeature.color?.let { setupColor(it) }
|
||||||
// graphics.drawString(textFeature.text, textFeature.position.x, textFeature.position.y)
|
graphics.drawString(textFeature.text, textFeature.position.x, textFeature.position.y)
|
||||||
// }
|
}
|
||||||
|
|
||||||
override fun painterFor(feature: PainterFeature<T>): Painter {
|
override fun painterFor(feature: PainterFeature<XY>): Painter {
|
||||||
return painterCache[feature]!!
|
return painterCache[feature]!!
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,194 @@
|
|||||||
|
package space.kscience.maps.svg
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.graphics.painter.Painter
|
||||||
|
import androidx.compose.ui.unit.DpSize
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import org.jfree.svg.SVGGraphics2D
|
||||||
|
import org.jfree.svg.SVGUtils
|
||||||
|
import space.kscience.attributes.Attributes
|
||||||
|
import space.kscience.attributes.plus
|
||||||
|
import space.kscience.maps.features.*
|
||||||
|
import space.kscience.maps.scheme.XY
|
||||||
|
import space.kscience.maps.scheme.XYCanvasState
|
||||||
|
|
||||||
|
|
||||||
|
public class FeatureStateSnapshot<T : Any>(
|
||||||
|
public val features: Map<String, Feature<T>>,
|
||||||
|
internal val painterCache: Map<PainterFeature<T>, Painter>,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
public fun <T : Any> FeatureSet<T>.snapshot(): FeatureStateSnapshot<T> = FeatureStateSnapshot(
|
||||||
|
features,
|
||||||
|
features.values.filterIsInstance<PainterFeature<T>>().associateWith { it.getPainter() }
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
public fun FeatureStateSnapshot<XY>.generateSvg(
|
||||||
|
viewPoint: ViewPoint<XY>,
|
||||||
|
width: Double,
|
||||||
|
height: Double,
|
||||||
|
id: String? = null,
|
||||||
|
): String {
|
||||||
|
|
||||||
|
// fun XY.toOffset(): Offset = Offset(
|
||||||
|
// (width / 2 + (x - viewPoint.focus.x) * viewPoint.zoom).toFloat(),
|
||||||
|
// (height / 2 + (viewPoint.focus.y - y) * viewPoint.zoom).toFloat()
|
||||||
|
// )
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// fun SvgDrawScope.drawFeature(scale: Float, feature: Feature<XY>) {
|
||||||
|
//
|
||||||
|
// val color = feature.color ?: Color.Red
|
||||||
|
// val alpha = feature.attributes[AlphaAttribute] ?: 1f
|
||||||
|
//
|
||||||
|
// when (feature) {
|
||||||
|
// is ScalableImageFeature -> {
|
||||||
|
// val offset = XY(feature.rectangle.left, feature.rectangle.top).toOffset()
|
||||||
|
// val backgroundSize = Size(
|
||||||
|
// (feature.rectangle.width * scale),
|
||||||
|
// (feature.rectangle.height * scale)
|
||||||
|
// )
|
||||||
|
//
|
||||||
|
// translate(offset.x, offset.y) {
|
||||||
|
// with(painterCache[feature]!!) {
|
||||||
|
// draw(backgroundSize)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// is FeatureSelector -> drawFeature(scale, feature.selector(scale))
|
||||||
|
//
|
||||||
|
// is CircleFeature -> drawCircle(
|
||||||
|
// color,
|
||||||
|
// feature.radius.toPx(),
|
||||||
|
// center = feature.center.toOffset(),
|
||||||
|
// alpha = alpha
|
||||||
|
// )
|
||||||
|
//
|
||||||
|
// is LineFeature -> drawLine(
|
||||||
|
// color,
|
||||||
|
// feature.a.toOffset(),
|
||||||
|
// feature.b.toOffset(),
|
||||||
|
// alpha = alpha
|
||||||
|
// )
|
||||||
|
//
|
||||||
|
// is PointsFeature -> {
|
||||||
|
// val points = feature.points.map { it.toOffset() }
|
||||||
|
// drawPoints(
|
||||||
|
// points = points,
|
||||||
|
// color = color,
|
||||||
|
// strokeWidth = feature.attributes[StrokeAttribute] ?: Stroke.HairlineWidth,
|
||||||
|
// pointMode = PointMode.Points,
|
||||||
|
// pathEffect = feature.attributes[PathEffectAttribute],
|
||||||
|
// alpha = alpha
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// is MultiLineFeature -> {
|
||||||
|
// val points = feature.points.map { it.toOffset() }
|
||||||
|
// drawPoints(
|
||||||
|
// points = points,
|
||||||
|
// color = color,
|
||||||
|
// strokeWidth = feature.attributes[StrokeAttribute] ?: Stroke.HairlineWidth,
|
||||||
|
// pointMode = PointMode.Polygon,
|
||||||
|
// pathEffect = feature.attributes[PathEffectAttribute],
|
||||||
|
// alpha = alpha
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// is ArcFeature -> {
|
||||||
|
// val topLeft = feature.oval.leftTop.toOffset()
|
||||||
|
// val bottomRight = feature.oval.rightBottom.toOffset()
|
||||||
|
//
|
||||||
|
// val size = Size(abs(topLeft.x - bottomRight.x), abs(topLeft.y - bottomRight.y))
|
||||||
|
//
|
||||||
|
// drawArc(
|
||||||
|
// color = color,
|
||||||
|
// startAngle = feature.startAngle.degrees.toFloat(),
|
||||||
|
// sweepAngle = feature.arcLength.degrees.toFloat(),
|
||||||
|
// useCenter = false,
|
||||||
|
// topLeft = topLeft,
|
||||||
|
// size = size,
|
||||||
|
// style = Stroke(),
|
||||||
|
// alpha = alpha
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// is BitmapIconFeature -> drawImage(feature.image, feature.center.toOffset())
|
||||||
|
//
|
||||||
|
// is VectorIconFeature -> {
|
||||||
|
// val offset = feature.center.toOffset()
|
||||||
|
// val imageSize = feature.size.toSize()
|
||||||
|
// translate(offset.x - imageSize.width / 2, offset.y - imageSize.height / 2) {
|
||||||
|
// with(painterCache[feature]!!) {
|
||||||
|
// draw(imageSize, alpha = alpha)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// is TextFeature -> drawIntoCanvas { _ ->
|
||||||
|
// val offset = feature.position.toOffset()
|
||||||
|
// drawText(
|
||||||
|
// feature.text,
|
||||||
|
// offset.x + 5,
|
||||||
|
// offset.y - 5,
|
||||||
|
// java.awt.Font(null, PLAIN, 16),
|
||||||
|
// color
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// is DrawFeature -> {
|
||||||
|
// val offset = feature.position.toOffset()
|
||||||
|
// translate(offset.x, offset.y) {
|
||||||
|
// feature.drawFeature(this)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// is FeatureGroup -> {
|
||||||
|
// feature.featureMap.values.forEach {
|
||||||
|
// drawFeature(scale, it)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
val svgGraphics2D: SVGGraphics2D = SVGGraphics2D(width, height)
|
||||||
|
val svgCanvasState: XYCanvasState = XYCanvasState(ViewConfig()).apply {
|
||||||
|
this.viewPoint = viewPoint
|
||||||
|
this.canvasSize = DpSize(width.dp, height.dp)
|
||||||
|
}
|
||||||
|
val svgScope = SvgDrawScope(svgCanvasState, svgGraphics2D, painterCache)
|
||||||
|
|
||||||
|
svgScope.apply {
|
||||||
|
features.entries.sortedBy { it.value.z }
|
||||||
|
.filter { state.viewPoint.zoom in it.value.zoomRange }
|
||||||
|
.forEach { (id, feature) ->
|
||||||
|
val attributesCache = mutableMapOf<List<String>, Attributes>()
|
||||||
|
|
||||||
|
fun computeGroupAttributes(path: List<String>): Attributes = attributesCache.getOrPut(path){
|
||||||
|
if (path.isEmpty()) return Attributes.EMPTY
|
||||||
|
else if (path.size == 1) {
|
||||||
|
features[path.first()]?.attributes ?: Attributes.EMPTY
|
||||||
|
} else {
|
||||||
|
computeGroupAttributes(path.dropLast(1)) + (features[path.first()]?.attributes ?: Attributes.EMPTY)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val path = id.split("/")
|
||||||
|
drawFeature(feature, computeGroupAttributes(path.dropLast(1)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return svgGraphics2D.getSVGElement(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
public fun FeatureStateSnapshot<XY>.exportToSvg(
|
||||||
|
viewPoint: ViewPoint<XY>,
|
||||||
|
width: Double,
|
||||||
|
height: Double,
|
||||||
|
path: java.nio.file.Path,
|
||||||
|
) {
|
||||||
|
val svgString: String = generateSvg(viewPoint, width, height)
|
||||||
|
SVGUtils.writeToSVG(path.toFile(), svgString)
|
||||||
|
}
|
@ -1,52 +0,0 @@
|
|||||||
package space.kscience.maps.svg
|
|
||||||
|
|
||||||
import androidx.compose.ui.geometry.Size
|
|
||||||
import androidx.compose.ui.graphics.asSkiaBitmap
|
|
||||||
import androidx.compose.ui.graphics.painter.Painter
|
|
||||||
import androidx.compose.ui.text.TextMeasurer
|
|
||||||
import androidx.compose.ui.unit.DpSize
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import org.jetbrains.skia.Image
|
|
||||||
import org.jfree.svg.SVGUtils
|
|
||||||
import space.kscience.maps.features.FeatureSet
|
|
||||||
import space.kscience.maps.features.PainterFeature
|
|
||||||
import space.kscience.maps.features.ViewConfig
|
|
||||||
import space.kscience.maps.features.ViewPoint
|
|
||||||
import space.kscience.maps.scheme.XY
|
|
||||||
import space.kscience.maps.scheme.XYCanvasState
|
|
||||||
import java.nio.file.Path
|
|
||||||
import kotlin.io.path.writeBytes
|
|
||||||
|
|
||||||
public fun FeatureSet<XY>.exportToSvg(
|
|
||||||
viewPoint: ViewPoint<XY>,
|
|
||||||
painterCache: Map<PainterFeature<XY>, Painter>,
|
|
||||||
size: Size,
|
|
||||||
path: Path,
|
|
||||||
) {
|
|
||||||
val svgCanvasState: XYCanvasState = XYCanvasState(ViewConfig()).apply {
|
|
||||||
this.viewPoint = viewPoint
|
|
||||||
this.canvasSize = DpSize(size.width.dp, size.height.dp)
|
|
||||||
}
|
|
||||||
|
|
||||||
val svgString: String = generateSvg(svgCanvasState, painterCache)
|
|
||||||
SVGUtils.writeToSVG(path.toFile(), svgString)
|
|
||||||
}
|
|
||||||
|
|
||||||
public fun FeatureSet<XY>.exportToPng(
|
|
||||||
viewPoint: ViewPoint<XY>,
|
|
||||||
painterCache: Map<PainterFeature<XY>, Painter>,
|
|
||||||
textMeasurer: TextMeasurer,
|
|
||||||
size: Size,
|
|
||||||
path: Path,
|
|
||||||
) {
|
|
||||||
val svgCanvasState: XYCanvasState = XYCanvasState(ViewConfig()).apply {
|
|
||||||
this.viewPoint = viewPoint
|
|
||||||
this.canvasSize = DpSize(size.width.dp, size.height.dp)
|
|
||||||
}
|
|
||||||
|
|
||||||
val bitmap = generateBitmap(svgCanvasState, painterCache, textMeasurer, size)
|
|
||||||
|
|
||||||
Image.makeFromBitmap(bitmap.asSkiaBitmap()).encodeToData()?.bytes?.let {
|
|
||||||
path.writeBytes(it)
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user