All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.soywiz.korge.ext.swf.SwfLoaderMethod.kt Maven / Gradle / Ivy

There is a newer version: 0.19.2
Show newest version
package com.soywiz.korge.ext.swf

import com.codeazur.as3swf.SWF
import com.codeazur.as3swf.data.actions.ActionGotoFrame
import com.codeazur.as3swf.data.actions.ActionPlay
import com.codeazur.as3swf.data.actions.ActionStop
import com.codeazur.as3swf.data.consts.BitmapFormat
import com.codeazur.as3swf.exporters.ShapeExporter
import com.codeazur.as3swf.exporters.ShapeExporterBoundsBuilder
import com.codeazur.as3swf.tags.*
import com.soywiz.kmem.fill
import com.soywiz.korfl.abc.*
import com.soywiz.korge.animate.*
import com.soywiz.korge.render.TextureWithBitmapSlice
import com.soywiz.korge.view.BlendMode
import com.soywiz.korge.view.Views
import com.soywiz.korim.bitmap.Bitmap
import com.soywiz.korim.bitmap.Bitmap32
import com.soywiz.korim.bitmap.Bitmap8
import com.soywiz.korim.bitmap.BitmapChannel
import com.soywiz.korim.color.BGRA
import com.soywiz.korim.color.BGRA_5551
import com.soywiz.korim.color.ColorTransform
import com.soywiz.korim.format.readBitmap
import com.soywiz.korim.vector.GraphicsPath
import com.soywiz.kds.BitSet
import com.soywiz.korio.error.ignoreErrors
import com.soywiz.korio.lang.printStackTrace
import com.soywiz.korio.serialization.json.Json
import com.soywiz.korio.stream.FastByteArrayInputStream
import com.soywiz.korio.stream.openAsync
import com.soywiz.korio.util.substr
import com.soywiz.korma.Matrix2d
import com.soywiz.korma.geom.BoundsBuilder
import com.soywiz.korma.geom.Rectangle
import kotlin.math.abs
import kotlin.math.max
import kotlin.math.min

data class MinMaxDouble(
	var count: Int = 0,
	var min: Double = 0.0,
	var max: Double = 0.0
) {
	val isEmpty: Boolean get() = count == 0
	val isNotEmpty: Boolean get() = count > 0

	fun register(value: Double) {
		if (isEmpty) {
			min = value
			max = value
		} else {
			min = min(min, value)
			max = max(max, value)
		}
		count++
	}

	fun register(value: MinMaxDouble) {
		if (value.isNotEmpty) {
			register(value.min)
			register(value.max)
		}
	}
}

class SymbolAnalyzeInfo(val characterId: Int) {
	var hasNinePatch = false
	val parents = LinkedHashSet()
	val scaleBounds = MinMaxDouble()

	val globalScaleBounds: MinMaxDouble by lazy {
		val out = MinMaxDouble()
		if (parents.isEmpty()) {
			if (scaleBounds.isNotEmpty) {
				out.register(scaleBounds)
			} else {
				out.register(1.0)
			}
		} else {
			for (parent in parents) {
				if (parent.hasNinePatch) continue // Do not count ninePatches
				out.register(scaleBounds.min * parent.globalScaleBounds.min)
				out.register(scaleBounds.max * parent.globalScaleBounds.max)
			}
		}
		out
	}

	fun registerParent(characterId: SymbolAnalyzeInfo) {
		parents += characterId
	}

	fun registerScale(scaleX: Double, scaleY: Double) {
		scaleBounds.register(max(scaleX, scaleY))
	}

	fun registerMatrix(matrix: Matrix2d) {
		registerScale(abs(matrix.a), abs(matrix.d))
	}
}

class SWFShapeRasterizerRequest(
	val swf: SWF,
	val charId: Int,
	val shapeBounds: Rectangle,
	val export: (ShapeExporter) -> Unit,
	val config: SWFExportConfig
) {
	val path = GraphicsPath()
	fun getRasterizer(maxScale: Double): SWFShapeRasterizer {
		val adaptiveScaling = if (config.adaptiveScaling) maxScale else 1.0
		//val maxScale = if (maxScale != 0.0) 1.0 / maxScale else 1.0
		//println("SWFShapeRasterizerRequest: $charId: $adaptiveScaling : $config")
		return SWFShapeRasterizer(
			swf,
			config.debug,
			shapeBounds,
			export,
			rasterizerMethod = config.rasterizerMethod,
			antialiasing = config.antialiasing,
			//requestScale = config.exportScale / maxScale.clamp(0.0001, 4.0),
			requestScale = config.exportScale * adaptiveScaling,
			minSide = config.minShapeSide,
			maxSide = config.maxShapeSide,
			path = path
		)
	}
}

class SwfLoaderMethod(val views: Views, val config: SWFExportConfig) {
	lateinit var swf: SWF
	lateinit var lib: AnLibrary
	val classNameToTypes = hashMapOf()
	val classNameToTagId = hashMapOf()
	val shapesToPopulate = LinkedHashMap()
	val morphShapesToPopulate = arrayListOf()
	val morphShapeRatios = hashMapOf>()

	private val analyzerInfos = hashMapOf()

	fun analyzerInfo(id: Int): SymbolAnalyzeInfo {
		return analyzerInfos.getOrPut(id) { SymbolAnalyzeInfo(id) }
	}

	suspend fun load(data: ByteArray): AnLibrary {
		swf = SWF().loadBytes(data)
		val bounds = swf.frameSize.rect
		lib = AnLibrary(views, bounds.width.toInt(), bounds.height.toInt(), swf.frameRate)
		parseMovieClip(swf.tags, AnSymbolMovieClip(0, "MainTimeLine", findLimits(swf.tags)))
		for (symbol in symbols) lib.addSymbol(symbol)
		processAs3Actions()
		generateActualTimelines()
		lib.processSymbolNames()
		generateTextures()
		finalProcessing()
		return lib
	}

	private fun finalProcessing() {
		if (true) {
			//println("totalPlaceObject: $totalPlaceObject")
			//println("totalShowFrame: $totalShowFrame")
		}
	}

	fun getFrameTime(index0: Int) = (index0 * lib.msPerFrameDouble).toInt() * 1000

	suspend private fun generateActualTimelines() {
		for (symbol in lib.symbolsById.filterIsInstance()) {
			val swfTimeline = symbol.swfTimeline
			var justAfterStopOrStart = true
			var stateStartFrame = 0
			//println(swfTimeline.frames)
			//println("## Symbol: ${symbol.name} : $symbol : ${swfTimeline.frames.size})")

			data class Subtimeline(val index: Int, var totalFrames: Int = 0, var nextState: String? = null, var nextStatePlay: Boolean = true) {
				//val totalTime get() = getFrameTime(totalFrames - 1)
				//val totalTime get() = getFrameTime(totalFrames)
				val totalTime get() = getFrameTime(totalFrames)
			}

			data class FrameInfo(val subtimeline: Subtimeline, val frameInSubTimeline: Int, val stateName: String, val startSubtimeline: Boolean, val startNamedState: Boolean) {
				val timeInSubTimeline = getFrameTime(frameInSubTimeline)
			}

			val frameInfos = ArrayList(swfTimeline.frames.size)

			// Identify referenced frames
			val referencedFrames = hashSetOf()
			for (frame in swfTimeline.frames) {
				if (frame.hasGoto) {
					val goto = frame.actions.filterIsInstance().first()
					referencedFrames += goto.frame0
				}
			}

			// Create FrameInfo
			var flow = true
			var stateName = "default"
			var frameIndex = 0
			var subtimelineIndex = -1


			val subtimelines = arrayListOf()

			for (frame in swfTimeline.frames) {
				var startNamedState = false
				var startSubtimeline = false
				if (flow) {
					stateName = when {
						frame.isFirst -> "default"
						frame.name != null -> frame.name!!
						else -> "frame${frame.index0}"
					}
					frameIndex = 0
					subtimelineIndex++
					subtimelines += Subtimeline(subtimelineIndex)
					startNamedState = true
					startSubtimeline = true
				}

				if (frame.name != null) {
					stateName = frame.name!!
					startNamedState = true
				} else if (frame.index0 in referencedFrames) {
					stateName = "frame${frame.index0}"
					startNamedState = true
				}

				val subtimeline = subtimelines[subtimelineIndex]
				subtimeline.totalFrames++
				flow = frame.hasFlow

				frameInfos += FrameInfo(subtimeline, frameIndex, stateName, startSubtimeline, startNamedState)

				frameIndex++
			}

			// Compute flow
			for (frame in swfTimeline.frames) {
				val info = frameInfos[frame.index0]
				val isLast = frame.index0 == swfTimeline.frames.last().index0

				if (frame.hasFlow) {
					for (action in frame.actions) {
						when (action) {
							is MySwfFrame.Action.Goto -> {
								info.subtimeline.nextState = frameInfos[action.frame0].stateName
							}
							is MySwfFrame.Action.Stop -> {
								info.subtimeline.nextStatePlay = false
							}
							is MySwfFrame.Action.Play -> {
								info.subtimeline.nextStatePlay = true
							}
						}
					}
				} else {
					if (isLast) {
						info.subtimeline.nextState = "default"
						info.subtimeline.nextStatePlay = true
					}
				}
			}

			val totalDepths = symbol.limits.totalDepths
			var currentSubTimeline = AnSymbolMovieClipSubTimeline(totalDepths)

			val lastDepths = kotlin.arrayOfNulls(totalDepths)

			//println("-------------")
			for (frame in swfTimeline.frames) {
				val info = frameInfos[frame.index0]
				val currentTime = info.timeInSubTimeline
				//val isLast = frame.index0 == swfTimeline.frames.last().index0

				// Subtimelines
				if (info.startSubtimeline) {
					currentSubTimeline = AnSymbolMovieClipSubTimeline(totalDepths)
					lastDepths.fill(null)
					val subtimeline = info.subtimeline
					currentSubTimeline.totalTime = subtimeline.totalTime
					currentSubTimeline.nextState = subtimeline.nextState
					currentSubTimeline.nextStatePlay = subtimeline.nextStatePlay

					//println("$currentSubTimeline : $subtimeline")

					//println("currentSubTimeline.totalTime = info.subtimeline.totalTime <- ${info.subtimeline.totalTime}")
					if (frame.isFirst) {
						symbol.states["default"] = AnSymbolMovieClipState("default", currentSubTimeline, 0)
						symbol.states["frame0"] = AnSymbolMovieClipState("frame0", currentSubTimeline, 0)
					}
				}
				// States
				if (info.startNamedState) {
					currentSubTimeline.actions.add(info.timeInSubTimeline, AnEventAction(info.stateName))
					symbol.states[info.stateName] = AnSymbolMovieClipState(info.stateName, currentSubTimeline, info.timeInSubTimeline)
				}

				// Compute frame
				//println("$info: $frame")
				for (depth in frame.depths) {
					val n = depth.depth
					val lastDepth = lastDepths[n]
					if (depth != lastDepth) {
						//println(" - [$n]: $depth")
						currentSubTimeline.timelines[depth.depth].add(info.timeInSubTimeline, depth)
						lastDepths[n] = depth
					}
				}

				// Compute actions
				for (it in frame.actions) {
					when (it) {
						is MySwfFrame.Action.PlaySound -> {
							currentSubTimeline.actions.add(currentTime, AnPlaySoundAction(it.soundId))
						}
					}
				}
			}

			// Append first frame of next animation to the end of each animation for smooth transition
			//val frameTime = lib.msPerFrame * 1000
//
			//for (currSubtimeline in symbol.states.map { it.value.subTimeline }.distinct()) {
			//	val nextStateName = currSubtimeline.nextState
			//	if (nextStateName != null) {
			//		val nextState = symbol.states[nextStateName]!!
			//		for ((index, nextTimeline) in nextState.subTimeline.timelines.withIndex()) {
			//			val newFrame = AnSymbolTimelineFrame()
			//			val result = nextTimeline.find(nextState.startTime)
			//			val l = result.left
			//			val r = result.right
			//			val ratio = result.ratio
			//			if (l != null && r != null) {
			//				newFrame.setToInterpolated(l, r, ratio)
			//			} else if (l != null) {
			//				newFrame.copyFrom(l)
			//			} else if (r != null) {
			//				newFrame.copyFrom(r)
			//			}
//
			//			val currPropTimeline = currSubtimeline.timelines[index]
			//			//currSubtimeline.totalTime += frameTime
			//			currPropTimeline.add(currSubtimeline.totalTime, newFrame)
			//			//println(newFrame)
			//		}
//
			//	}
			//}
		}
	}

	suspend private fun processAs3Actions() {
		for ((className, tagId) in classNameToTagId) {
			lib.symbolsById[tagId].name = className
			val type = classNameToTypes[className] ?: continue
			val symbol = (lib.symbolsById[tagId] as? AnSymbolMovieClip?) ?: continue
			val abc = type.abc
			val labelsToFrame0 = symbol.labelsToFrame0

			//println("$tagId :: $className :: $symbol :: $type")
			for (trait in type.instanceTraits) {
				val simpleName = trait.name.simpleName
				//println(" - " + trait.name.simpleName)
				if (simpleName.startsWith("frame")) {
					val frame = ignoreErrors { simpleName.substr(5).toInt() } ?: continue
					val frame0 = frame - 1
					val traitMethod = (trait as ABC.TraitMethod?) ?: continue
					val methodDesc = abc.methodsDesc[traitMethod.methodIndex]
					val body = methodDesc.body ?: continue
					//println("FRAME: $frame0")
					//println(body.ops)

					var lastValue: Any? = null
					for (op in body.ops) {
						when (op.opcode) {
							AbcOpcode.PushByte -> lastValue = (op as AbcIntOperation).value
							AbcOpcode.PushShort -> lastValue = (op as AbcIntOperation).value
							AbcOpcode.PushInt -> lastValue = (op as AbcIntOperation).value
							AbcOpcode.PushUInt -> lastValue = (op as AbcIntOperation).value
							AbcOpcode.PushString -> lastValue = (op as AbcStringOperation).value
							AbcOpcode.CallPropVoid -> {
								val call = (op as AbcMultinameIntOperation)
								val callMethodName = call.multiname.simpleName
								val frameData = symbol.swfTimeline.frames[frame0]
								when (callMethodName) {
									"gotoAndPlay", "gotoAndStop" -> {
										val gotoFrame0 = when (lastValue) {
											is String -> labelsToFrame0[lastValue] ?: 0
											is Int -> lastValue - 1
											else -> 0
										}
										if (callMethodName == "gotoAndStop") {
											frameData.gotoAndStop(gotoFrame0)
										} else {
											frameData.gotoAndPlay(gotoFrame0)
										}
									}
									"play" -> frameData.play()
									"stop" -> frameData.stop()

									else -> {
										//println("method: $callMethodName")
									}
								}
								lastValue = null
							}
							else -> Unit
						}
					}
				}
			}
		}
	}


	suspend private fun generateTextures() {
		val itemsInAtlas = LinkedHashMap<(TextureWithBitmapSlice) -> Unit, BitmapWithScale>()

		for ((shape, rasterizerRequest) in shapesToPopulate) {
			val info = analyzerInfo(rasterizerRequest.charId)
			val rasterizer = rasterizerRequest.getRasterizer(info.globalScaleBounds.max)
			itemsInAtlas.put({ texture -> shape.textureWithBitmap = texture }, rasterizer.imageWithScale)
		}

		for (morph in morphShapesToPopulate) {
			val tag = morph.tagDefineMorphShape!!
			val ratios = (morphShapeRatios[tag.characterId] ?: setOf()).sorted()
			val MAX_RATIOS = 24
			val aratios = if (ratios.size > MAX_RATIOS) (0 until MAX_RATIOS).map { it.toDouble() / (MAX_RATIOS - 1).toDouble() } else ratios
			for (ratio in aratios) {
				val bb = ShapeExporterBoundsBuilder()
				try {
					tag.export(bb, ratio)
				} catch (e: Throwable) {
					e.printStackTrace()
				}
				val bounds = bb.bb.getBounds()
				//bb.bb.add()
				val rasterizer = SWFShapeRasterizer(
					swf, config.debug, bounds,
					{
						try {
							tag.export(it, ratio)
						} catch (e: Throwable) {
							e.printStackTrace()
						}
					},
					config.rasterizerMethod,
					antialiasing = config.antialiasing,
					requestScale = config.exportScale,
					minSide = config.minMorphShapeSide,
					maxSide = config.maxMorphShapeSide
				)
				itemsInAtlas.put({ texture -> morph.texturesWithBitmap.add((ratio * 1000).toInt(), texture) }, rasterizer.imageWithScale)
			}
		}

		for ((processor, texture) in itemsInAtlas.toAtlas(views, config.maxTextureSide, config.mipmaps)) processor(texture)
	}

	fun findLimits(tags: Iterable): AnSymbolLimits {
		var maxDepth = -1
		var totalFrames = 0
		val items = hashSetOf>()
		// Find limits
		for (it in tags) {
			when (it) {
				is TagPlaceObject -> {
					if (it.hasCharacter) {
						items += it.depth0 to it.characterId
					}

					maxDepth = max(maxDepth, it.depth0)
					if (it.hasClipDepth) {
						maxDepth = max(maxDepth, it.clipDepth0 + 1)
					}
					//if (it.hasClipDepth) maxDepth = max(maxDepth, it.clipDepth0)
				}
				is TagShowFrame -> {
					totalFrames++
				}
			}
		}
		return AnSymbolLimits(maxDepth + 1, totalFrames, items.size, (totalFrames * lib.msPerFrameDouble).toInt())
	}

	val symbols = arrayListOf()

	fun registerBitmap(charId: Int, bmp: Bitmap, name: String? = null) {
		swf.bitmaps[charId] = bmp
		symbols += AnSymbolBitmap(charId, name, bmp)
		//showImageAndWait(bmp)
	}

	var totalPlaceObject = 0
	var globalTotalShowFrame = 0

	var spritesById = hashMapOf()

	suspend fun parseMovieClip(tags: Iterable, mc: AnSymbolMovieClip) {
		symbols += mc

		val swfTimeline = mc.swfTimeline
		val labelsToFrame0 = mc.labelsToFrame0
		val uniqueIds = hashMapOf, Int>()

		data class DepthInfo(
			val depth: Int,
			var uid: Int = -1,
			var charId: Int = -1,
			var clipDepth: Int = -1,
			var name: String? = null,
			var colorTransform: ColorTransform = ColorTransform.identity,
			var ratio: Double = 0.0,
			var matrix: Matrix2d = Matrix2d(),
			var blendMode: BlendMode = BlendMode.INHERIT
		) {
			fun reset() {
				uid = -1
				ratio = 0.0
				charId = -1
				clipDepth = -1
				colorTransform = ColorTransform.identity
				name = null
				matrix = Matrix2d()
				blendMode = BlendMode.INHERIT
			}

			//var frameElement = AnSymbolTimelineFrame(); private set

			fun createFrameElement() {
				//frameElement = toFrameElement()
			}

			fun toFrameElement() = AnSymbolTimelineFrame(
				depth = depth,
				clipDepth = clipDepth,
				uid = uid,
				ratio = ratio,
				name = name,
				transform = matrix,
				colorTransform = colorTransform,
				blendMode = blendMode
			)
		}

		val depths = Array(mc.limits.totalDepths) { DepthInfo(it) }

		fun getUid(depth: Int): Int {
			val charId = depths[depth].charId
			return uniqueIds.getOrPut(depth to charId) {
				val uid = uniqueIds.size
				mc.uidInfo[uid] = AnSymbolUidDef(charId)
				uid
			}
		}

		// Add frames and read labels information
		var totalShowFramesInMc = 0
		for (it in tags) {
			val currentFrame = swfTimeline.frames.size
			when (it) {
				is TagDefineSceneAndFrameLabelData -> {
					mc.labelsToFrame0 += it.frameLabels.map { it.name to it.frameNumber - 1 }
				}
				is TagFrameLabel -> {
					mc.labelsToFrame0[it.frameName] = currentFrame
				}
				is TagShowFrame -> {
					swfTimeline.frames += MySwfFrame(currentFrame, mc.limits.totalDepths)
					totalShowFramesInMc++
				}
			}
		}

		//if (totalShowFramesInMc > 0) swfTimeline.frames += MySwfFrame(swfTimeline.frames.size, mc.limits.totalDepths)

		// Populate frame names
		for ((name, index) in mc.labelsToFrame0) swfTimeline.frames[index].name = name

		val depthsChanged = BitSet(depths.size)
		var currentFrame = 0
		for (it in tags) {
			//println("Tag: $it")
			val currentTime = getFrameTime(currentFrame)
			val swfCurrentFrame by lazy { mc.swfTimeline.frames[currentFrame] }
			when (it) {
				is TagDefineSceneAndFrameLabelData -> Unit
				is TagFrameLabel -> Unit
				is TagFileAttributes -> Unit
				is TagSetBackgroundColor -> {
					lib.bgcolor = decodeSWFColor(it.color)
				}
				is TagProtect -> Unit // ignore
				is TagDefineFont -> {
				}
				is TagDefineFontName -> {
				}
				is TagDefineFontAlignZones -> {
				}
				is TagDefineEditText -> {
					symbols += AnTextFieldSymbol(it.characterId, null, it.initialText ?: "", it.bounds.rect)
				}
				is TagCSMTextSettings -> {
				}
				is TagDoAction -> {
					for (action in it.actions) {
						when (action) {
							is ActionStop -> swfCurrentFrame.stop()
							is ActionPlay -> swfCurrentFrame.play()
							is ActionGotoFrame -> swfCurrentFrame.goto(action.frame)
						}
					}
				}
				is TagSoundStreamHead -> {
				}
				is TagDefineSound -> {
					val soundBytes = it.soundData.cloneToNewByteArray()
					symbols += AnSymbolSound(it.characterId, null, null, soundBytes)
				}
				is TagStartSound -> {
					swfCurrentFrame.playSound(it.soundId)
				}
				is TagJPEGTables -> {
					println("Unhandled tag: $it")
				}
				is TagDefineBits, is TagDefineBitsLossless -> {
					var fbmp: Bitmap = Bitmap32(1, 1)
					it as IDefinitionTag

					when (it) {
						is TagDefineBitsJPEG2 -> {
							val bitsData = it.bitmapData.cloneToNewByteArray()
							val nativeBitmap = try {
								bitsData.openAsync().readBitmap()
							} catch (e: Throwable) {
								e.printStackTrace()
								Bitmap32(1, 1)
							}
							//println(nativeBitmap)
							val bmp = nativeBitmap.toBMP32()
							fbmp = bmp

							if (it is TagDefineBitsJPEG3) {
								val fmaskinfo = it.bitmapAlphaData.cloneToNewFlashByteArray()
								fmaskinfo.uncompressInWorker("zlib")
								val maskinfo = fmaskinfo.cloneToNewByteArray()
								//val bmpAlpha = nativeImageFormatProvider.decode(maskinfo)
								//showImageAndWait(bmpAlpha)
								bmp.writeChannel(BitmapChannel.ALPHA, Bitmap8(bmp.width, bmp.height, maskinfo))
							}

							//showImageAndWait(bmp)

							//println(bmp)
							//for (y in 0 until bmp.height) {
							//	for (x in 0 until bmp.width) System.out.printf("%08X,", bmp[x, y])
							//	println()
							//}
						}
						is TagDefineBitsLossless -> {
							//val isRgba = it.hasAlpha
							val funcompressedData = it.zlibBitmapData.cloneToNewFlashByteArray()
							funcompressedData.uncompressInWorker("zlib")
							val uncompressedData = funcompressedData.cloneToNewByteArray()
							when (it.bitmapFormat) {
								BitmapFormat.BIT_8 -> {
									val ncolors = it.bitmapColorTableSize
									val s = FastByteArrayInputStream(uncompressedData)
									val clut = if (it.hasAlpha) {
										(0 until ncolors).map { s.readS32_le() }.toIntArray()
									} else {
										(0 until ncolors).map { 0x00FFFFFF.inv() or s.readU24_le() }.toIntArray()
									}
									val pixels = s.readBytes(it.actualWidth * it.actualHeight)

									val bmp = Bitmap8(it.actualWidth, it.actualHeight, pixels, clut)
									fbmp = bmp
								}
								BitmapFormat.BIT_15 -> {
									fbmp = Bitmap32(it.actualWidth, it.actualHeight, BGRA_5551.decode(uncompressedData))
								}
								BitmapFormat.BIT_24_32 -> {
									val components = uncompressedData.size / (it.bitmapWidth * it.bitmapHeight)
									val colorFormat = BGRA
									//fbmp = Bitmap32(it.bitmapWidth, it.bitmapHeight, colorFormat.decode(uncompressedData, littleEndian = false))
									fbmp = Bitmap32(it.bitmapWidth, it.bitmapHeight, colorFormat.decode(uncompressedData, littleEndian = false))
									if (!it.hasAlpha) {
										for (n in 0 until fbmp.data.size) fbmp.data[n] = 0x00FFFFFF.inv() or (fbmp.data[n] and 0x00FFFFFF)
									}
								}
								else -> Unit
							}

						}
					}

					registerBitmap(it.characterId, fbmp, null)
				}
				is TagDefineShape -> {
					val tag = it
					val rasterizerRequest = SWFShapeRasterizerRequest(swf, tag.characterId, tag.shapeBounds.rect, { tag.export(it) }, config)
					//val rasterizer = LoggerShapeExporter(SWFShapeRasterizer(swf, debug, it))
					val symbol = AnSymbolShape(it.characterId, null, rasterizerRequest.shapeBounds, null, rasterizerRequest.path)
					symbol.tagDefineShape = it
					symbols += symbol
					shapesToPopulate += symbol to rasterizerRequest
				}
				is TagDefineMorphShape -> {
					val startBounds = it.startBounds.rect
					val endBounds = it.endBounds.rect
					val bounds = BoundsBuilder()
						.add(startBounds)
						.add(endBounds)
						.add(it.startEdgeBounds.rect)
						.add(it.endEdgeBounds.rect)
						.getBounds()

					val bounds2 = bounds.copy(width = bounds.width + 100, height = bounds.height + 100)

					//println("${startBounds.toStringBounds()}, ${endBounds.toStringBounds()} -> ${bounds.toStringBounds()}")

					val symbol = AnSymbolMorphShape(it.characterId, null, bounds2)
					symbol.tagDefineMorphShape = it
					symbols += symbol
					morphShapesToPopulate += symbol
				}
				is TagDoABC -> {
					classNameToTypes += it.abc.typesInfo.map { it.name.toString() to it }.toMap()
				}
				is TagSymbolClass -> {
					classNameToTagId += it.symbols.filter { it.name != null }.map { it.name!! to it.tagId }.toMap()
				}
				is TagDefineSprite -> {
					val childMc = AnSymbolMovieClip(it.characterId, null, findLimits(it.tags))
					spritesById[it.characterId] = childMc
					parseMovieClip(it.tags, childMc)
				}
				is TagDefineScalingGrid -> {
					val childMc = spritesById[it.characterId]
					if (childMc != null) {
						childMc.ninePatch = it.splitter.rect
					}
					analyzerInfo(it.characterId).hasNinePatch = true
				}
				is TagPlaceObject -> {
					totalPlaceObject++
					//val depthId = if (it.hasClipDepth) it.clipDepth0 else it.depth0
					//val clipDepthId = if (it.hasClipDepth) it.depth0 else -1

					val depthId = it.depth0
					val clipDepthId = if (it.hasClipDepth) it.clipDepth0 - 1 else -1

					val depth = depths[depthId]

					if (it.hasCharacter) depth.charId = it.characterId
					if (it.hasClipDepth) depth.clipDepth = clipDepthId
					if (it.hasName) depth.name = it.instanceName
					//if (it.hasBlendMode) depth.blendMode = it.blendMode
					if (it.hasColorTransform) {
						val ct = it.colorTransform!!.toColorTransform()
						depth.colorTransform = ct
						//allColorTransforms += ct
						//println(depth.colorTransform)
					}
					if (it.hasMatrix) {
						val m = it.matrix!!.matrix
						depth.matrix = m
						//allMatrices += m
					}
					if (it.hasBlendMode) depth.blendMode = when (it.blendMode) {
						SwfBlendMode.NORMAL_0 -> BlendMode.NORMAL
						SwfBlendMode.NORMAL_1 -> BlendMode.NORMAL
						SwfBlendMode.LAYER -> BlendMode.INHERIT
						SwfBlendMode.MULTIPLY -> BlendMode.MULTIPLY
						SwfBlendMode.SCREEN -> BlendMode.SCREEN
						SwfBlendMode.LIGHTEN -> BlendMode.LIGHTEN
						SwfBlendMode.DARKEN -> BlendMode.DARKEN
						SwfBlendMode.DIFFERENCE -> BlendMode.DIFFERENCE
						SwfBlendMode.ADD -> BlendMode.ADD
						SwfBlendMode.SUBTRACT -> BlendMode.SUBTRACT
						SwfBlendMode.INVERT -> BlendMode.INVERT
						SwfBlendMode.ALPHA -> BlendMode.ALPHA
						SwfBlendMode.ERASE -> BlendMode.ERASE
					//SwfBlendMode.OVERLAY ->  BlendMode.OVERLAY
						SwfBlendMode.OVERLAY -> BlendMode.INHERIT
						SwfBlendMode.HARDLIGHT -> BlendMode.HARDLIGHT
						else -> BlendMode.INHERIT
					}
					val uid = getUid(depthId)
					val metaData = it.metaData
					if (metaData != null && metaData is Map<*, *> && "props" in metaData) {
						val uidInfo = mc.uidInfo[uid]
						val eprops = ignoreErrors { Json.decode(metaData["props"].toString()) as Map }
						if (eprops != null) uidInfo.extraProps += eprops
						//println(depth.extraProps)
					}

					if (it.hasRatio) {
						depth.ratio = it.ratiod
						val ratios = morphShapeRatios.getOrPut(depth.charId) { hashSetOf() }
						ratios += it.ratiod
					}

					analyzerInfo(depth.charId).registerParent(analyzerInfo(mc.id))
					analyzerInfo(depth.charId).registerMatrix(depth.matrix)

					depth.uid = uid
					depthsChanged[depthId] = true
					//depth.createFrameElement()
				}
				is TagRemoveObject -> {
					depths[it.depth0].reset()
					depthsChanged[it.depth0] = true
					//depths[it.depth0].createFrameElement()
				}
				is TagShowFrame -> {
					globalTotalShowFrame++
					for (depth in depths) {
						//if (depthsChanged[depth.depth]) swfCurrentFrame.depths += depth.toFrameElement()
						//swfCurrentFrame.depths += depth.frameElement
						swfCurrentFrame.depths += depth.toFrameElement()
					}
					depthsChanged.clear()
					currentFrame++
				}
				is TagEnd -> {
				}
				else -> {
					println("Unhandled tag $it")
				}
			}
		}

		//if (totalShowFramesInMc > 0) {
		//	val lastFrame = mc.swfTimeline.frames.last()
		//	for (depth in depths) lastFrame.depths += depth.toFrameElement()
		//}

	}
}

private typealias SwfBlendMode = com.codeazur.as3swf.data.consts.BlendMode





© 2015 - 2024 Weber Informatics LLC | Privacy Policy