Skip to content

Commit

Permalink
feat(plugin-image): add gif split
Browse files Browse the repository at this point in the history
  • Loading branch information
Leon406 committed Dec 16, 2022
1 parent 13aa2f8 commit f88f649
Show file tree
Hide file tree
Showing 6 changed files with 146 additions and 5 deletions.
116 changes: 116 additions & 0 deletions plugin-image/src/main/kotlin/me/leon/toolsfx/plugin/ext/GifSplitter.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
package me.leon.toolsfx.plugin.ext

import java.awt.*
import java.awt.image.BufferedImage
import java.io.File
import javax.imageio.ImageIO
import javax.imageio.metadata.IIOMetadataNode

/**
* https://stackoverflow.com/questions/8933893/convert-each-animated-gif-frame-to-a-separate-bufferedimage
*/
fun File.splitGif(): String {
val outputDir = File(parent, nameWithoutExtension)
outputDir.mkdirs()
val reader =
ImageIO.getImageReadersByFormatName("gif").next().apply {
input = ImageIO.createImageInputStream(inputStream())
}

var size = Rectangle()

val metadata = reader.streamMetadata
var backgroundColor: Color? = null
if (metadata != null) {
val globalRoot = metadata.getAsTree(metadata.nativeMetadataFormatName) as IIOMetadataNode
size = globalRoot.parseSize()
backgroundColor = globalRoot.parseBackground()
}
var masterImage: BufferedImage? = null
var frameIndex = 0

while (true) {
val image: BufferedImage =
try {
reader.read(frameIndex)
} catch (ignored: Exception) {
break
}

if (size.isEmpty) {
size.width = image.width
size.height = image.height
}
val root =
reader.getImageMetadata(frameIndex).getAsTree("javax_imageio_gif_image_1.0")
as IIOMetadataNode
val children = root.childNodes
if (masterImage == null) {
masterImage =
BufferedImage(size.width, size.height, BufferedImage.TYPE_INT_ARGB).also {
it.createGraphics().apply {
color = backgroundColor
fillRect(0, 0, size.width, size.height)
drawImage(image, 0, 0, null)
}
}
} else {
val currentPoint = Point()
for (nodeIndex in 0 until children.length) {
val nodeItem = children.item(nodeIndex)
if (nodeItem.nodeName.equals("ImageDescriptor")) {
val map = nodeItem.attributes
currentPoint.x =
Integer.valueOf(map.getNamedItem("imageLeftPosition").nodeValue)
currentPoint.y = Integer.valueOf(map.getNamedItem("imageTopPosition").nodeValue)
break
}
}
masterImage.createGraphics()?.drawImage(image, currentPoint.x, currentPoint.y, null)
ImageIO.write(masterImage, "PNG", File(outputDir, "$frameIndex.png"))
}
masterImage.flush()
frameIndex++
}

reader.dispose()
return "saved to ${outputDir.absolutePath}"
}

private fun IIOMetadataNode.parseBackground(backgroundColor: Color? = null): Color? {
var tmp = backgroundColor
val globalColorTable = getElementsByTagName("GlobalColorTable")
if (globalColorTable.length > 0) {
val colorTable = globalColorTable.item(0) as IIOMetadataNode?
if (colorTable != null) {
val bgIndex = colorTable.getAttribute("backgroundColorIndex")
var colorEntry = colorTable.firstChild as IIOMetadataNode?
while (colorEntry != null) {
if (colorEntry.getAttribute("index") == bgIndex) {
tmp = colorEntry.color()
break
}
colorEntry = colorEntry.nextSibling as IIOMetadataNode
}
}
}
return tmp
}

private fun IIOMetadataNode.parseSize(): Rectangle {
var rect = Rectangle()
val globalScreeDescriptor = getElementsByTagName("LogicalScreenDescriptor")

if (globalScreeDescriptor.length > 0) {
val screenDescriptor = globalScreeDescriptor.item(0) as IIOMetadataNode
rect =
Rectangle(
screenDescriptor.getAttribute("logicalScreenWidth").toInt(),
screenDescriptor.getAttribute("logicalScreenHeight").toInt()
)
}
return rect
}

private fun IIOMetadataNode.color() =
Color(getAttribute("red").toInt(), getAttribute("green").toInt(), getAttribute("blue").toInt())
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package me.leon.toolsfx.plugin.ext

import me.leon.encode.base.base64
import me.leon.ext.fx.base64Image
import me.leon.ext.toFile

enum class ImageServiceType(val type: String) : ImageService {
FIX_PNG("fix png") {
Expand All @@ -28,6 +29,10 @@ enum class ImageServiceType(val type: String) : ImageService {
override fun process(raw: String, isFile: Boolean, params: Map<String, String>) =
raw.properString(isFile).rgb()
},
GIF_SPLIT("gif split") {
override fun process(raw: String, isFile: Boolean, params: Map<String, String>) =
raw.toFile().splitGif()
},
}

val serviceTypeMap = ImageServiceType.values().associateBy { it.type }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
package me.leon.toolsfx.plugin.ext

import java.awt.image.BufferedImage
import java.math.BigInteger
import javafx.scene.image.Image
import javafx.scene.paint.Color
import kotlin.math.sqrt
import me.leon.ctf.rsa.factor
import me.leon.ext.*
import me.leon.ext.crypto.crc32
import me.leon.ext.fx.toFxImg
import java.awt.image.BufferedImage
import java.math.BigInteger
import kotlin.math.sqrt

/**
* @author Leon
Expand Down
11 changes: 11 additions & 0 deletions plugin-image/src/test/kotlin/me/leon/img/ImageFrame.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package me.leon.img

import java.awt.image.BufferedImage

data class ImageFrame(
val image: BufferedImage?,
val delay: Int,
val disposal: String?,
val width: Int,
val height: Int
)
11 changes: 10 additions & 1 deletion plugin-image/src/test/kotlin/me/leon/img/ImageTest.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package me.leon.img

import kotlin.test.Test
import me.leon.ext.toFile
import me.leon.toolsfx.plugin.ext.splitGif
import org.junit.Ignore
import tornadofx.*

/**
Expand All @@ -10,8 +13,14 @@ import tornadofx.*
class ImageTest {

@Test
// @Ignore
@Ignore
fun base64() {
launch<Main>()
}

@Test
@Ignore
fun gifSplit() {
"E:\\gitrepo\\ToolsFx\\art\\ctf.gif".toFile().splitGif()
}
}
2 changes: 1 addition & 1 deletion plugin-image/src/test/kotlin/me/leon/img/Main.kt
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ class Main : Application() {

// val image = File(IMG_DIR,"qr01").readText().binaryImage(false)
// val image = File(IMG_DIR, "bin2").readText().binaryImage()
val image = File("E:\\download\\360\\1-raw.png").fixPng().toImage()
val image = File("E:\\download\\360\\1-raw.png").readBytes().fixPng().toImage()
// val image = File(IMG_DIR, "base64").readText().base64Image()

// val text = File(IMG_DIR, "rgb.txt").readText()
Expand Down

0 comments on commit f88f649

Please sign in to comment.