拼接漫画图片

master
pan 4 years ago
parent cbc9a26b7d
commit 93bf0c9730
  1. 2
      src/commonMain/kotlin/Data.kt
  2. 74
      src/jsMain/kotlin/image.kt
  3. 35
      src/jsMain/kotlin/welcome.kt
  4. 24
      src/jvmMain/kotlin/plugins/Routing.kt

@ -34,4 +34,4 @@ data class UrlParam(val url:String,val html:String)
const val websocketPath="/webSocket" const val websocketPath="/webSocket"
const val websiteTitle="朴实无华的漫画解析工具" const val websiteTitle="朴实无华的takeshobo漫画解析工具"

@ -1,6 +1,8 @@
import kotlinext.js.getOwnPropertyNames import kotlinext.js.getOwnPropertyNames
import kotlinx.browser.document import kotlinx.browser.document
import kotlinx.css.head import kotlinx.browser.window
import org.khronos.webgl.ArrayBuffer
import org.khronos.webgl.DataView
import org.khronos.webgl.Uint8Array import org.khronos.webgl.Uint8Array
import org.khronos.webgl.set import org.khronos.webgl.set
import org.w3c.dom.CanvasRenderingContext2D import org.w3c.dom.CanvasRenderingContext2D
@ -15,7 +17,7 @@ import kotlin.math.ceil
import kotlin.math.max import kotlin.math.max
import kotlin.math.min import kotlin.math.min
import kotlin.math.round import kotlin.math.round
import org.w3c.dom.WindowOrWorkerGlobalScope
data class un(val width: Int, val height: Int) data class un(val width: Int, val height: Int)
@ -130,6 +132,8 @@ class ImageLoader(val urlResult: UrlResult){
private var imageHeight=0.0 private var imageHeight=0.0
private val tasks= mutableListOf<Promise<Image>>()
//speedbinb.js?dmy=016301:formatted:8766 //speedbinb.js?dmy=016301:formatted:8766
private fun callback(t:un): List<n> { private fun callback(t:un): List<n> {
d.urlResult=urlResult d.urlResult=urlResult
@ -198,39 +202,76 @@ class ImageLoader(val urlResult: UrlResult){
} }
} }
Promise<Image>(executor = { resolve, _ ->
canvasToBlob(t=canvas,{blob: Blob -> canvasToBlob(t=canvas,{blob: Blob ->
console.info("blob.size:${blob.size}") console.info("blob.size:${blob.size}")
val i=URL.createObjectURL(blob=blob) val i=URL.createObjectURL(blob=blob)
console.info("漫画URL:${i}") console.info("加载图片dataUrl:\n${i}")
Image().apply { Image().apply {
onload={event: Event -> onload={event: Event ->
canvasHtml.let { imageHeight+=this.naturalHeight
canvas-> resolve(this)
val ctx:CanvasRenderingContext2D= canvas.getContext("2d") as CanvasRenderingContext2D
ctx.drawImage(this,0.0,imageHeight,naturalWidth.toDouble(),naturalHeight.toDouble().apply {
imageHeight+=this
console.info("拼接图片imageHeight:${imageHeight}")
})
}
} }
src=i src=i
} }
}) })
}).apply {
tasks.add(this)
}
}
fun dataURItoBlob(dataURI:String): Blob {
// convert base64 to raw binary data held in a string
val byteString = window.atob(dataURI.split(',')[1])
// separate out the mime component
val mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0]
// write the bytes of the string to an ArrayBuffer
val arrayBuffer = ArrayBuffer(byteString.length)
val _ia = Uint8Array(arrayBuffer)
byteString.withIndex().forEach {
_ia[it.index] = it.value.code.toByte()
} }
fun create(){ val dataView = DataView(arrayBuffer);
val blob = Blob(arrayOf(dataView),options = BlobPropertyBag(type = mimeString));
return blob
}
private fun create(){
Promise.all(promise = tasks.toTypedArray()).then {
canvasHtml.let {
canvas->
canvas.width=it.first().naturalWidth
canvas.height=imageHeight.toInt()
val ctx:CanvasRenderingContext2D= canvas.getContext("2d") as CanvasRenderingContext2D
ctx.clearRect(0.0, 0.0, canvas.width.toDouble(), canvas.height.toDouble())
var dy=0.0
it.forEach {
ctx.drawImage(image=it,dx=0.0,dy=dy,
dw=it.naturalWidth.toDouble(),dh=it.naturalHeight.toDouble().apply {
dy+=this.toInt()-1
})
}
Image().apply { Image().apply {
onload={event: Event -> onload={
console.info("拼接图片大小naturalWidth:${naturalWidth},naturalHeight:${naturalHeight}") console.info("拼接图片大小naturalWidth:${naturalWidth},naturalHeight:${naturalHeight}")
} }
src=canvasHtml.toDataURL() src=canvas.toDataURL().let { URL.createObjectURL(dataURItoBlob(it)) }.apply {
console.info("拼接图片url:\n$this")
}
} }
} }
}
}
//speedbinb.js?dmy=016301:formatted:3798 //speedbinb.js?dmy=016301:formatted:3798
fun canvasToBlob( private fun canvasToBlob(
t:HTMLCanvasElement, callback:(t:Blob)->Unit, n:String="image/jpeg", r: Double =.9){ t:HTMLCanvasElement, callback:(t:Blob)->Unit, n:String="image/jpeg", r: Double =.9){
val i=t.toDataURL(type=n,quality = r).split(",")[1] val i=t.toDataURL(type=n,quality = r).split(",")[1]
console.info("url length:${i.length}") console.info("url length:${i.length}")
@ -282,6 +323,7 @@ class ImageLoader(val urlResult: UrlResult){
// val n=t(width = t.width,height = t.height) // val n=t(width = t.width,height = t.height)
us(t=t,image=it) us(t=t,image=it)
} }
create()
} }
} }

@ -26,7 +26,7 @@ external interface WelcomeProps : RProps {
var webSocket:WebSocket var webSocket:WebSocket
} }
data class WelcomeState(var url:String="",var result:String="",var percentage:kotlin.Float=0F) : RState data class WelcomeState(var url:String="",var result:String="",var percentage:kotlin.Float=0F,var allowInput:Boolean=true) : RState
fun Double.format(digits: Int): String = this.asDynamic().toFixed(digits) fun Double.format(digits: Int): String = this.asDynamic().toFixed(digits)
fun Float.format(digits: Int): String = this.asDynamic().toFixed(digits) fun Float.format(digits: Int): String = this.asDynamic().toFixed(digits)
@ -43,13 +43,11 @@ class Welcome(props: WelcomeProps) : RComponent<WelcomeProps, WelcomeState>(prop
if(data.contains("ptimg-version")){ if(data.contains("ptimg-version")){
val urlResult=Json.decodeFromString<UrlResult>(data) val urlResult=Json.decodeFromString<UrlResult>(data)
console.info("ptimg_version:${urlResult.t.ptimg_version}") console.info("ptimg_version:${urlResult.t.ptimg_version}")
ImageLoader(urlResult = urlResult).apply { ImageLoader(urlResult = urlResult).rebuild()
rebuild()
create()
}
}else{ }else{
val task=Json.decodeFromString<ParseTask>(data) val task=Json.decodeFromString<ParseTask>(data)
state.result="解析进度:${task.percentage}%" state.result="解析进度:${task.percentage}%"
state.allowInput=(state.percentage==100F)
setState(state) setState(state)
} }
} }
@ -72,9 +70,9 @@ class Welcome(props: WelcomeProps) : RComponent<WelcomeProps, WelcomeState>(prop
href="https://gammaplus.takeshobo.co.jp" href="https://gammaplus.takeshobo.co.jp"
target="_blank" target="_blank"
} }
+"漫画" +"takeshobo"
} }
+"解析工具" +"漫画解析工具"
} }
styledDiv{ styledDiv{
@ -93,6 +91,7 @@ class Welcome(props: WelcomeProps) : RComponent<WelcomeProps, WelcomeState>(prop
attrs{ attrs{
type=InputType.text type=InputType.text
value = state.url value = state.url
disabled = !state.allowInput
onChangeFunction = { event -> onChangeFunction = { event ->
(event.target as HTMLInputElement).let { (event.target as HTMLInputElement).let {
console.info(it.value) console.info(it.value)
@ -117,36 +116,42 @@ class Welcome(props: WelcomeProps) : RComponent<WelcomeProps, WelcomeState>(prop
button { button {
attrs { attrs {
disabled=!state.allowInput
onClickFunction={ onClickFunction={
state.allowInput=false
state.result="初始化解析任务请稍等"
setState(state)
val formData=FormData() val formData=FormData()
formData.append("url",state.url) formData.append("url",state.url)
window.fetch("/api/json", RequestInit(method = "post",body = formData)).then { window.fetch("/api/json", RequestInit(method = "post",body = formData))
.then {
it.text() it.text()
}.then { }.then {
console.info(it) console.info(it)
state.result=Json.decodeFromString<MessageResponse>(it).message
setState(state)
} }
} }
} }
+"开始解析" +"开始解析"
} }
if(state.result!=""){ div {
styledDiv {
+state.result +state.result
} }
if(state.percentage>0F&&state.percentage<100F){
button { button {
attrs { attrs {
onClickFunction={ onClickFunction={
state.result="" props.webSocket.send("cancel")
state=WelcomeState()
setState(state) setState(state)
} }
} }
+"清空消息" +"取消解析任务"
} }
} }
} }
} }

@ -11,6 +11,8 @@ import io.ktor.request.*
import io.ktor.response.* import io.ktor.response.*
import io.ktor.routing.* import io.ktor.routing.*
import io.ktor.websocket.* import io.ktor.websocket.*
import kotlinx.coroutines.Job
import kotlinx.coroutines.cancel
import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.html.HTML import kotlinx.html.HTML
@ -25,6 +27,7 @@ const val website = "https://gammaplus.takeshobo.co.jp"
var taskChannel = Channel<ParseTask>() var taskChannel = Channel<ParseTask>()
val urlResultChannel = Channel<UrlResult>() val urlResultChannel = Channel<UrlResult>()
val htmlChannel= Channel<UrlParam>() val htmlChannel= Channel<UrlParam>()
var currentJob: Job?=null
fun Application.parse(){ fun Application.parse(){
log.info("初始化解析任务") log.info("初始化解析任务")
@ -35,6 +38,11 @@ fun Application.parse(){
launch { launch {
while (true){ while (true){
val resHtml= htmlChannel.receive() val resHtml= htmlChannel.receive()
currentJob?.let {
if(it.isActive) this.cancel()
}
launch { launch {
log.info("开始解析:${resHtml.url}") log.info("开始解析:${resHtml.url}")
val client = HttpClient(CIO) val client = HttpClient(CIO)
@ -82,10 +90,12 @@ fun Application.parse(){
} }
client.close() client.close()
}.apply { }.apply {
currentJob=this
invokeOnCompletion { invokeOnCompletion {
log.info("${resHtml.url}解析完成") log.info("${resHtml.url}解析完成")
} }
} }
} }
} }
@ -120,11 +130,19 @@ fun Application.configureRouting() {
when (frame) { when (frame) {
is Frame.Text -> { is Frame.Text -> {
val text = frame.readText() val text = frame.readText()
outgoing.send(Frame.Text("YOU SAID: $text")) when {
if (text.equals("bye", ignoreCase = true)) { "cancel" == text -> {
close(CloseReason(CloseReason.Codes.NORMAL, "Client said BYE")) if (currentJob?.isActive == true) {
currentJob?.cancel()
outgoing.send(Frame.Text("当前服务器解析任务已停止"))
} else {
outgoing.send(Frame.Text("当前服务器没有正在运行的解析任务"))
}
}
"exit" == text -> close(CloseReason(CloseReason.Codes.NORMAL, "Client said exit"))
} }
} }
else -> log.warn("无法处理${frame.frameType}类型消息")
} }
} }
} }

Loading…
Cancel
Save