|
@@ -2,52 +2,110 @@ package com.bomostory.sceneeditmodule
|
|
|
|
|
|
import android.content.Context
|
|
|
import android.graphics.*
|
|
|
-import android.util.DisplayMetrics
|
|
|
-import android.view.WindowManager
|
|
|
import com.bomostory.sceneeditmodule.basicdata.Actor
|
|
|
import com.bomostory.sceneeditmodule.basicdata.Scene
|
|
|
+import pl.droidsonroids.gif.GifDrawable
|
|
|
+import java.util.concurrent.locks.ReentrantLock
|
|
|
+import kotlin.math.max
|
|
|
import kotlin.math.pow
|
|
|
|
|
|
object SceneDrawer {
|
|
|
- fun drawScene(context: Context, scene: Scene, trackX: Int, scaleWidth: Int, scaleHeight: Int): Bitmap? {
|
|
|
- var sceneBitmap: Bitmap? = null
|
|
|
+ private val CONTROLLER_RADIUS = 25
|
|
|
+ private var nowScene: Scene? = null
|
|
|
+ private var sceneBitmap: Bitmap? = null
|
|
|
+ private var bitmaps = HashMap<Actor, Any>()
|
|
|
+ private var gifFrameInfo = HashMap<Actor, IntArray>()
|
|
|
+ private var widthScaleFactor = 1f
|
|
|
+ private var heightScaleFactor = 1f
|
|
|
|
|
|
+ private val lock = ReentrantLock()
|
|
|
+
|
|
|
+ fun reset() {
|
|
|
+ nowScene = null
|
|
|
+ sceneBitmap = null
|
|
|
+ bitmaps.clear()
|
|
|
+ gifFrameInfo.clear()
|
|
|
+
|
|
|
+ if (lock.isLocked)
|
|
|
+ lock.unlock()
|
|
|
+ }
|
|
|
+
|
|
|
+ fun drawScene(context: Context, scene: Scene, trackX: Int, scaleWidth: Int, scaleHeight: Int, millisecond: Long = -1): Bitmap? {
|
|
|
+ lock.lock()
|
|
|
+ if (nowScene != scene) {
|
|
|
+ reset()
|
|
|
+ nowScene = scene
|
|
|
+ val screenWidth = scene.sceneWidth.toFloat()
|
|
|
+ val screenHeight = scene.sceneWidth / 2f
|
|
|
+ widthScaleFactor = scaleWidth / screenWidth
|
|
|
+ heightScaleFactor = scaleHeight / screenHeight
|
|
|
+ loadBitmaps(context, scaleWidth, scaleHeight, millisecond >= 0)
|
|
|
+ } else {
|
|
|
+ lock.unlock()
|
|
|
+ }
|
|
|
+
|
|
|
+ val bitmap = Bitmap.createBitmap(scaleWidth, scaleHeight, Bitmap.Config.ARGB_8888)
|
|
|
scene?.apply {
|
|
|
- sceneBitmap = BitmapFactory.decodeFile(backgroundPath)
|
|
|
- sceneBitmap = Bitmap.createScaledBitmap(sceneBitmap, scaleWidth, scaleHeight, true)
|
|
|
- sceneBitmap = sceneBitmap?.copy(Bitmap.Config.ARGB_8888, true)
|
|
|
+ val canvas = Canvas(bitmap)
|
|
|
+ canvas.save()
|
|
|
+ canvas?.drawBitmap(sceneBitmap, trackX * widthScaleFactor / 32f - sceneBitmap!!.width / 64f, 0f, null)
|
|
|
+ canvas.restore()
|
|
|
|
|
|
- val metrics = DisplayMetrics()
|
|
|
- val windowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
|
|
|
- windowManager.defaultDisplay.getMetrics(metrics);
|
|
|
- val screenWidth = metrics.widthPixels
|
|
|
- val screenHeight = metrics.widthPixels / 2
|
|
|
- val widthScaleFactor: Float = scaleWidth / screenWidth.toFloat()
|
|
|
- val heightScaleFactor: Float = scaleHeight / screenHeight.toFloat()
|
|
|
- val canvas = Canvas(sceneBitmap)
|
|
|
for (layer in layers) {
|
|
|
+ canvas.save()
|
|
|
+ canvas.translate(trackX * widthScaleFactor / 2f.pow(layers.indexOf(layer)), 0f)
|
|
|
for (actor in layer.actors) {
|
|
|
- var bitmap = if (actor.isDialogue) DialogueDrawer.drawDialogue(context, actor) else BitmapFactory.decodeFile(actor.resourcePath)
|
|
|
+ var bitmap = bitmaps[actor]
|
|
|
if (bitmap != null) {
|
|
|
- canvas.save()
|
|
|
- canvas.translate(trackX / 2f.pow(layers.indexOf(layer)), 0f)
|
|
|
- drawActor(canvas, actor, bitmap, widthScaleFactor, heightScaleFactor)
|
|
|
- canvas.restore()
|
|
|
- if (!bitmap.isRecycled) {
|
|
|
- bitmap.recycle()
|
|
|
+ if (bitmap is GifData) {
|
|
|
+ val gifData = bitmap
|
|
|
+ if (gifData.frameIndex < 0 || !(millisecond in gifData.durations[gifData.frameIndex])) {
|
|
|
+ var gifTime = millisecond
|
|
|
+ gifData.frameIndex = gifData.durations.size - 1
|
|
|
+ if (gifData.gifDrawable.loopCount <= 0 || gifTime < gifData.gifDrawable.loopCount * gifData.gifDrawable.duration) {
|
|
|
+ gifTime %= gifData.gifDrawable.duration
|
|
|
+
|
|
|
+ for (index in 0 until gifData.durations.size) {
|
|
|
+ if (gifTime in gifData.durations[index]) {
|
|
|
+ gifData.frameIndex = index
|
|
|
+ break
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ val seekBitmap = gifData.gifDrawable.seekToFrameAndGet(gifData.frameIndex)
|
|
|
+ var actorWidth = (actor.sideLength - CONTROLLER_RADIUS * 4).toFloat()
|
|
|
+ var actorHeight = (actor.sideHeight - CONTROLLER_RADIUS * 4).toFloat()
|
|
|
+ if (seekBitmap.width > seekBitmap.height) {
|
|
|
+ actorHeight = actorHeight * seekBitmap.height / seekBitmap.width
|
|
|
+ } else {
|
|
|
+ actorWidth = actorWidth * seekBitmap.width / seekBitmap.height
|
|
|
+ }
|
|
|
+ val bitmapWidth = actorWidth * widthScaleFactor
|
|
|
+ val bitmapHeight = actorHeight * heightScaleFactor
|
|
|
+ gifData.bitmap = Bitmap.createScaledBitmap(seekBitmap, bitmapWidth.toInt(), bitmapHeight.toInt(), true)
|
|
|
+ seekBitmap.recycle()
|
|
|
+ }
|
|
|
+ drawActor(canvas, actor, gifData.bitmap, widthScaleFactor, heightScaleFactor)
|
|
|
+ } else {
|
|
|
+ drawActor(canvas, actor, bitmap as Bitmap, widthScaleFactor, heightScaleFactor)
|
|
|
}
|
|
|
- bitmap = null
|
|
|
}
|
|
|
}
|
|
|
+ canvas.restore()
|
|
|
}
|
|
|
}
|
|
|
- return sceneBitmap
|
|
|
+
|
|
|
+ if (millisecond < 0) {
|
|
|
+ reset()
|
|
|
+ }
|
|
|
+ if (lock.isLocked)
|
|
|
+ lock.unlock()
|
|
|
+
|
|
|
+ return bitmap
|
|
|
}
|
|
|
|
|
|
private fun drawActor(canvas: Canvas, actor: Actor, bitmap: Bitmap, widthScaleFactor: Float, heightScaleFactor: Float) {
|
|
|
-
|
|
|
- val CONTROLLER_RADIUS = 25
|
|
|
- var drawBitmap: Bitmap? = null
|
|
|
if (!actor.isDialogue) {
|
|
|
var actorWidth = (actor.sideLength - CONTROLLER_RADIUS * 4).toFloat()
|
|
|
var actorHeight = (actor.sideHeight - CONTROLLER_RADIUS * 4).toFloat()
|
|
@@ -56,9 +114,8 @@ object SceneDrawer {
|
|
|
} else {
|
|
|
actorWidth = actorWidth * bitmap.width / bitmap.height
|
|
|
}
|
|
|
- var bitmapWidth = actorWidth * widthScaleFactor
|
|
|
- var bitmapHeight = actorHeight * heightScaleFactor
|
|
|
- drawBitmap = Bitmap.createScaledBitmap(bitmap, bitmapWidth.toInt(), bitmapHeight.toInt(), true)
|
|
|
+ val bitmapWidth = actorWidth * widthScaleFactor
|
|
|
+ val bitmapHeight = actorHeight * heightScaleFactor
|
|
|
|
|
|
var actorX = (actor.positionX + CONTROLLER_RADIUS * 2).toFloat()
|
|
|
var actorY = (actor.positionY + CONTROLLER_RADIUS * 2).toFloat()
|
|
@@ -71,23 +128,68 @@ object SceneDrawer {
|
|
|
paint.alpha = (actor.opacity * 255).toInt()
|
|
|
if (actor.isMirror)
|
|
|
canvas.scale(-1f, 1f, actorX + bitmapWidth / 2, actorY + bitmapHeight / 2)
|
|
|
- canvas.drawBitmap(drawBitmap, actorX, actorY, paint)
|
|
|
+ canvas.drawBitmap(bitmap, actorX, actorY, paint)
|
|
|
} else {
|
|
|
- var actorWidth = (actor.sideLength).toFloat()
|
|
|
- var actorHeight = (actor.sideHeight).toFloat()
|
|
|
- var bitmapWidth = actorWidth * widthScaleFactor
|
|
|
- var bitmapHeight = actorHeight * heightScaleFactor
|
|
|
- drawBitmap = Bitmap.createScaledBitmap(bitmap, bitmapWidth.toInt(), bitmapHeight.toInt(), true)
|
|
|
-
|
|
|
var actorX = (actor.positionX).toFloat()
|
|
|
var actorY = (actor.positionY).toFloat()
|
|
|
actorX *= widthScaleFactor
|
|
|
actorY *= heightScaleFactor
|
|
|
- canvas?.drawBitmap(drawBitmap, actorX, actorY, null)
|
|
|
+ canvas?.drawBitmap(bitmap, actorX, actorY, null)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private fun GifDrawable.getBitmapAt(milliseconds: Int): Bitmap = seekToPositionAndGet(if (loopCount == 0 || milliseconds < duration * loopCount) max(0, milliseconds % duration) else (duration))
|
|
|
+
|
|
|
+ private fun getBitmap(context: Context, actor: Actor, createGifData: Boolean): Any {
|
|
|
+ if (!actor.isDialogue) {
|
|
|
+ val isGif = actor.resourcePath.toLowerCase().endsWith(".gif")
|
|
|
+ if (isGif && createGifData) {
|
|
|
+ val gifDrawable = GifDrawable(actor.resourcePath)
|
|
|
+ val size = gifDrawable.numberOfFrames
|
|
|
+ var durations = Array(size, { index -> 0L .. 1L} )
|
|
|
+ var count = 0L
|
|
|
+ for (index in 0 until size) {
|
|
|
+ var duration = gifDrawable.getFrameDuration(index).toLong()
|
|
|
+ durations[index] = count until (count + duration)
|
|
|
+ count += duration
|
|
|
+ }
|
|
|
+ return GifData(gifDrawable, durations, -1, gifDrawable.currentFrame)
|
|
|
+ }
|
|
|
+ var bitmap = if (isGif) GifDrawable(actor.resourcePath).currentFrame else BitmapFactory.decodeFile(actor.resourcePath)
|
|
|
+ var actorWidth = (actor.sideLength - CONTROLLER_RADIUS * 4).toFloat()
|
|
|
+ var actorHeight = (actor.sideHeight - CONTROLLER_RADIUS * 4).toFloat()
|
|
|
+ if (bitmap.width > bitmap.height) {
|
|
|
+ actorHeight = actorHeight * bitmap.height / bitmap.width
|
|
|
+ } else {
|
|
|
+ actorWidth = actorWidth * bitmap.width / bitmap.height
|
|
|
+ }
|
|
|
+ val bitmapWidth = actorWidth * widthScaleFactor
|
|
|
+ val bitmapHeight = actorHeight * heightScaleFactor
|
|
|
+ bitmap = Bitmap.createScaledBitmap(bitmap, bitmapWidth.toInt(), bitmapHeight.toInt(), true)
|
|
|
+ return bitmap
|
|
|
+ } else {
|
|
|
+ var bitmap = DialogueDrawer.drawDialogue(context, actor)!!
|
|
|
+ val actorWidth = (actor.sideLength).toFloat()
|
|
|
+ val actorHeight = (actor.sideHeight).toFloat()
|
|
|
+ val bitmapWidth = actorWidth * widthScaleFactor
|
|
|
+ val bitmapHeight = actorHeight * heightScaleFactor
|
|
|
+ bitmap = Bitmap.createScaledBitmap(bitmap, bitmapWidth.toInt(), bitmapHeight.toInt(), true)
|
|
|
+ return bitmap
|
|
|
}
|
|
|
- if (!drawBitmap.isRecycled) {
|
|
|
- drawBitmap.recycle()
|
|
|
+ }
|
|
|
+
|
|
|
+ private fun loadBitmaps(context: Context, scaleWidth: Int, scaleHeight: Int, createGifData: Boolean) {
|
|
|
+ nowScene?.apply {
|
|
|
+ sceneBitmap = BitmapFactory.decodeFile(backgroundPath)
|
|
|
+ sceneBitmap = Bitmap.createScaledBitmap(sceneBitmap, (scaleWidth * 33 / 32f).toInt(), scaleHeight, true)
|
|
|
+ sceneBitmap = sceneBitmap?.copy(Bitmap.Config.ARGB_8888, true)
|
|
|
+ for (layer in layers) {
|
|
|
+ for (actor in layer.actors) {
|
|
|
+ bitmaps[actor] = getBitmap(context, actor, createGifData)
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
- drawBitmap = null
|
|
|
}
|
|
|
+
|
|
|
+ data class GifData(val gifDrawable: GifDrawable, val durations: Array<LongRange>, var frameIndex: Int, var bitmap: Bitmap)
|
|
|
}
|