com.soywiz.korge.ext.swf.SWFShapeRasterizer.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of korge-ext-swf-common Show documentation
Show all versions of korge-ext-swf-common Show documentation
Korge: Kotlin cORoutines Game Engine
package com.soywiz.korge.ext.swf
import com.codeazur.as3swf.SWF
import com.codeazur.as3swf.data.GradientType
import com.codeazur.as3swf.data.SWFColorTransform
import com.codeazur.as3swf.data.consts.GradientInterpolationMode
import com.codeazur.as3swf.data.consts.GradientSpreadMode
import com.codeazur.as3swf.data.consts.LineCapsStyle
import com.codeazur.as3swf.exporters.LoggerShapeExporter
import com.codeazur.as3swf.exporters.ShapeExporter
import com.soywiz.korim.bitmap.Bitmap32
import com.soywiz.korim.bitmap.NativeImage
import com.soywiz.korim.color.ColorTransform
import com.soywiz.korim.color.Colors
import com.soywiz.korim.color.RGBA
import com.soywiz.korim.vector.*
import com.soywiz.korio.util.clamp
import com.soywiz.korio.util.extract8
import com.soywiz.korio.util.toIntCeil
import com.soywiz.korma.Matrix2d
import com.soywiz.korma.ds.DoubleArrayList
import com.soywiz.korma.ds.IntArrayList
import com.soywiz.korma.geom.Rectangle
import kotlin.math.max
import kotlin.math.min
/**
* @TODO: Line ScaleMode not supported right now.
* @TODO: Default behaviour for strokes:
* @TODO: - smaller keeps at least 1 pixel
* @TODO: - bigger: ScaleMode.NONE - keeps the size, ScaleMode.NORMAL - scales the stroke
* @TODO: It would be possible to emulate using another texture with distances + colors
* @TODO: But probably no worth
*/
class SWFShapeRasterizer(
val swf: SWF,
val debug: Boolean,
val bounds: Rectangle,
val export: (ShapeExporter) -> Unit,
val rasterizerMethod: Context2d.ShapeRasterizerMethod,
val antialiasing: Boolean,
val requestScale: Double = 2.0,
val minSide: Int = 16,
val maxSide: Int = 512,
val path: GraphicsPath = GraphicsPath()
) : ShapeExporter() {
//val bounds: Rectangle = dshape.shapeBounds.rect
//val bmp = Bitmap32(bounds.width.toIntCeil(), bounds.height.toIntCeil())
val realBoundsWidth = max(1, bounds.width.toIntCeil())
val realBoundsHeight = max(1, bounds.height.toIntCeil())
val desiredBoundsWidth = (realBoundsWidth * requestScale).toInt()
val desiredBoundsHeight = (realBoundsHeight * requestScale).toInt()
val limitBoundsWidth = desiredBoundsWidth.clamp(minSide, maxSide)
val limitBoundsHeight = desiredBoundsHeight.clamp(minSide, maxSide)
val actualScale = min(limitBoundsWidth.toDouble() / realBoundsWidth.toDouble(), limitBoundsHeight.toDouble() / realBoundsHeight.toDouble())
//val actualScale = 0.5
val actualBoundsWidth = (realBoundsWidth * actualScale).toInt()
val actualBoundsHeight = (realBoundsHeight * actualScale).toInt()
var cshape = CompoundShape(listOf())
val shapes = arrayListOf()
val actualShape by lazy {
export(if (debug) LoggerShapeExporter(this) else this)
//this.dshape.export(if (debug) LoggerShapeExporter(this) else this)
cshape
}
val image by lazy {
val image = NativeImage(actualBoundsWidth, actualBoundsHeight)
val ctx = image.getContext2d(antialiasing = antialiasing)
ctx.scale(actualScale, actualScale)
ctx.translate(-bounds.x, -bounds.y)
//ctx.lineScaleHack *= 20.0
//ctx.lineScaleHack *= requestScale
//ctx.lineScaleHack *= 1.0
//ctx.lineScaleHack *= 2.0
ctx.drawShape(actualShape, rasterizerMethod)
//println(actualShape.toSvg(scale = 1.0 / 20.0).toOuterXmlIndented())
image
}
val imageWithScale by lazy {
BitmapWithScale(image, actualScale, bounds)
}
var drawingFill = true
var apath = GraphicsPath()
override fun beginShape() {
//ctx.beginPath()
}
override fun endShape() {
cshape = CompoundShape(shapes)
//ctx.closePath()
}
override fun beginFills() {
flush()
drawingFill = true
}
override fun endFills() {
flush()
}
override fun beginLines() {
flush()
drawingFill = false
}
override fun endLines() {
flush()
}
fun GradientSpreadMode.toCtx() = when (this) {
GradientSpreadMode.PAD -> Context2d.CycleMethod.NO_CYCLE
GradientSpreadMode.REFLECT -> Context2d.CycleMethod.REFLECT
GradientSpreadMode.REPEAT -> Context2d.CycleMethod.REPEAT
}
var fillStyle: Context2d.Paint = Context2d.None
override fun beginFill(color: Int, alpha: Double) {
flush()
drawingFill = true
fillStyle = Context2d.Color(decodeSWFColor(color, alpha))
}
private fun createGradientPaint(type: GradientType, colors: List, alphas: List, ratios: List, matrix: Matrix2d, spreadMethod: GradientSpreadMode, interpolationMethod: GradientInterpolationMode, focalPointRatio: Double): Context2d.Gradient {
val aratios = DoubleArrayList(ratios.map { it.toDouble() / 255.0 }.toDoubleArray())
val acolors = IntArrayList(colors.zip(alphas).map { decodeSWFColor(it.first, it.second) }.toIntArray())
val m2 = Matrix2d()
m2.copyFrom(matrix)
m2.pretranslate(-0.5, -0.5)
m2.prescale(1638.4 / 2.0, 1638.4 / 2.0)
m2.scale(20.0, 20.0)
//m2.prescale(1.0 / 20.0, 1.0 / 20.0)
//m2.prescale(1.0 / 20.0, 1.0 / 20.0)
val imethod = when (interpolationMethod) {
GradientInterpolationMode.NORMAL -> Context2d.Gradient.InterpolationMethod.NORMAL
GradientInterpolationMode.LINEAR -> Context2d.Gradient.InterpolationMethod.LINEAR
}
return when (type) {
GradientType.LINEAR -> Context2d.LinearGradient(-1.0, 0.0, +1.0, 0.0, aratios, acolors, spreadMethod.toCtx(), m2, imethod)
GradientType.RADIAL -> Context2d.RadialGradient(focalPointRatio, 0.0, 0.0, 0.0, 0.0, 1.0, aratios, acolors, spreadMethod.toCtx(), m2, imethod)
}
}
override fun beginGradientFill(type: GradientType, colors: List, alphas: List, ratios: List, matrix: Matrix2d, spreadMethod: GradientSpreadMode, interpolationMethod: GradientInterpolationMode, focalPointRatio: Double) {
flush()
drawingFill = true
fillStyle = createGradientPaint(type, colors, alphas, ratios, matrix, spreadMethod, interpolationMethod, focalPointRatio)
}
override fun beginBitmapFill(bitmapId: Int, matrix: Matrix2d, repeat: Boolean, smooth: Boolean) {
flush()
drawingFill = true
val bmp = swf.bitmaps[bitmapId] ?: Bitmap32(1, 1)
//fillStyle = Context2d.BitmapPaint(bmp, matrix.clone(), repeat, smooth)
fillStyle = Context2d.BitmapPaint(bmp, matrix.clone().scale(20.0, 20.0), repeat, smooth)
//fillStyle = Context2d.BitmapPaint(bmp, matrix.clone().prescale(20.0, 20.0), repeat, smooth)
}
override fun endFill() {
flush()
}
private fun __flushFill() {
if (apath.isEmpty()) return
shapes += FillShape(apath, null, fillStyle, Matrix2d().prescale(1.0 / 20.0, 1.0 / 20.0))
apath = GraphicsPath()
}
private fun __flushStroke() {
if (apath.isEmpty()) return
shapes += PolylineShape(apath, null, strokeStyle, Matrix2d().prescale(1.0 / 20.0, 1.0 / 20.0), lineWidth, true, Context2d.ScaleMode.NORMAL, lineCap, lineCap, "joints", miterLimit)
apath = GraphicsPath()
}
private fun flush() {
if (drawingFill) {
__flushFill()
} else {
__flushStroke()
}
}
private var lineWidth: Double = 1.0
private var lineScaleMode = Context2d.ScaleMode.NORMAL
private var miterLimit = 1.0
private var lineCap: Context2d.LineCap = Context2d.LineCap.ROUND
private var strokeStyle: Context2d.Paint = Context2d.Color(Colors.BLACK)
override fun lineStyle(thickness: Double, color: Int, alpha: Double, pixelHinting: Boolean, scaleMode: Context2d.ScaleMode, startCaps: LineCapsStyle, endCaps: LineCapsStyle, joints: String?, miterLimit: Double) {
flush()
this.drawingFill = false
//println("pixelHinting: $pixelHinting, scaleMode: $scaleMode, miterLimit=$miterLimit")
this.lineWidth = thickness * 20.0
this.lineScaleMode = scaleMode
this.miterLimit = miterLimit
this.strokeStyle = Context2d.Color(decodeSWFColor(color, alpha))
this.lineCap = when (startCaps) {
LineCapsStyle.NO -> Context2d.LineCap.BUTT
LineCapsStyle.ROUND -> Context2d.LineCap.ROUND
LineCapsStyle.SQUARE -> Context2d.LineCap.SQUARE
}
}
override fun lineGradientStyle(type: GradientType, colors: List, alphas: List, ratios: List, matrix: Matrix2d, spreadMethod: GradientSpreadMode, interpolationMethod: GradientInterpolationMode, focalPointRatio: Double) {
flush()
drawingFill = false
strokeStyle = createGradientPaint(type, colors, alphas, ratios, matrix, spreadMethod, interpolationMethod, focalPointRatio)
}
private fun Double.fix() = (this * 20).toInt().toDouble()
//private fun Double.fix() = this.toInt()
override fun moveTo(x: Double, y: Double) {
apath.moveTo(x.fix(), y.fix())
if (drawingFill) path.moveTo(x, y)
}
override fun lineTo(x: Double, y: Double) {
apath.lineTo(x.fix(), y.fix())
if (drawingFill) path.lineTo(x, y)
}
override fun curveTo(controlX: Double, controlY: Double, anchorX: Double, anchorY: Double) {
apath.quadTo(controlX.fix(), controlY.fix(), anchorX.fix(), anchorY.fix())
if (drawingFill) path.quadTo(controlX, controlY, anchorX, anchorY)
}
override fun closePath() {
apath.close()
if (drawingFill) path.close()
}
}
fun SWFColorTransform.toColorTransform() = ColorTransform(rMult, gMult, bMult, aMult, rAdd, gAdd, bAdd, aAdd)
fun decodeSWFColor(color: Int, alpha: Double = 1.0) = RGBA.pack(color.extract8(16), color.extract8(8), color.extract8(0), (alpha * 255).toInt())