diff --git a/README.md b/README.md index 0cbb082..b6b3a9b 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,18 @@ # BotPlugin -一个运行于[Mirai Console](https://github.com/mamoe/mirai-console)的插件 + +一个运行于[Mirai Console](https://github.com/mamoe/mirai-console)的插件 + A plugin for [Mirai Console](https://github.com/mamoe/mirai-console) with image sender,pixiv querying and more. + +## petpet-rs 部署 + +请在 [petpet-rs](https://github.com/poly000/petpet-rs/release/latest) 下载对应平台的二进制,以及 `res` + +并把 petpet 二进制文件(以 `petpet(.exe)` 命名)放在 `mirai` 运行的目录,`res` 中的文件放在此目录的同名子目录。 + ## 功能 -* 发送本地图库的圖片(请手动下载赫图,另提供指令sendImage一次发送多张)(发送带有'图'和'来'的消息或者色图来图片(不知道这图我也没办法,你可以用图片ID发出去)) + +* 发送本地图库的圖片(请手动下载赫图,另提供指令sendImage一次发送多张)(发送带有'图'和'来'的消息或者色图来图片(不知道这图我也没办法,你可以用图片ID发出去)) * 发送在线的Json源图片 * 发送在线的Pixiv的圖片(允许设置代理,说得好像不设置代理谁进得去呢?) * 被At时复读,并且把At的对象换成对方(此外还支持颠倒消息顺序,随机旋转消息图片) @@ -13,47 +23,69 @@ A plugin for [Mirai Console](https://github.com/mamoe/mirai-console) with image * 插件管理员执行Javascript脚本(可用变量:bot,event,dlg,dlg请查看ScriptMethods.kt获取具体用法) * 随机复读 * 没了 + ## 特色 + * 单线程撤回发出的本地图片(防止请求过于频繁被服务器拒绝) -* CommandDispatcher(?蜜汁操作,指令派发) -**在本仓库的Releases中可以直接下载编译好的jar文件! 如果不是需要研究,你可以直接下载** +* CommandDispatcher(?蜜汁操作,指令派发) + +### 在本仓库的Releases中可以直接下载编译好的jar文件! 如果不是需要研究,你可以直接下载 + ## 简单使用 -- 下载Release里发布的jar. -- [启动Mirai-Console](https://github.com/mamoe/mirai-console/blob/master/docs/Run.md) -- 复制jar到Console工作目录下的plugins里 -- 见下方'开始使用'栏目 -### 当前项目环境: -* Mirai 2.6.5 -* Kotlin 1.5.10 -* OpenJdk 14 -* Intellij IDEA 2021.1.2 -**注意! 在编译本项目之前,请留意删除gradle.properties中的网络代理设置** + +* 下载Release里发布的jar. +* [启动Mirai-Console](https://github.com/mamoe/mirai-console/blob/master/docs/Run.md) +* 复制jar到Console工作目录下的plugins里 +* 见下方'开始使用'栏目 + +### 当前项目环境 + +* Mirai 2.6.5 +* Kotlin 1.5.10 +* OpenJdk 14 +* Intellij IDEA 2021.1.2 + +### 注意! 在编译本项目之前,请留意删除gradle.properties中的网络代理设置 + ## 开始使用 -建议使用 Java 11或者更高版本的Java -先带着插件运行一次Mirai Console,然后停止 -在`mirai文件夹/config/RosemoeBotPlugin/PluginConfig.yml`中把 + +建议使用 Java 11或者更高版本的Java +先带着插件运行一次Mirai Console,然后停止 +在`mirai文件夹/config/RosemoeBotPlugin/PluginConfig.yml`中把 + ```yml managers: [] ``` + 修改为 + ```yml managers: - 你的QQ ``` -当然也可以添加多个Manager,比如 + +当然也可以添加多个Manager,比如 + ```yml managers: - 114514 - 1919810 - 12345678 -``` +``` + 然后重新运行Mirai Console + ## 群聊指令表 + 非常建议您先读完下面的指令表再使用 + ### 设置部分 + 只有Plugin Manager(不是Bot Manager)才能使用这些指令! + #### 全局设置 -```Bash + +```text /settings set recallDelay <时间> /settings set recallInterval <时间> /settings set prefix <指令前缀,默认'/'> @@ -67,33 +99,105 @@ managers: /settings enable <功能> /settings disable <功能> ``` + reloadBase只刷新配置不重新建立图片索引,算是轻重载 + #### 图片源设置 + 图片源保存在image_sources.yml中 -```Bash + 添加一个路径: + +``` /sources path <名称> <路径> +``` 添加一个在线Json图源 + +``` /sources json <名称> <网址> <数据路径> +``` + 对应的网址要返回一个Json文本,其中通过数据路径可以到达url元素 + 比如返回下面这段Json: -{"code":1,"msg":"ok","data":"http:\/\/test.xxx.com\/large\/a15b4afegy1fmvjv7pshlj21hc0u0e0s.jpg"} + +```json +{ + "code": 1, + "msg": "ok", + "data": "http://test.xxx.com/large/a15b4afegy1fmvjv7pshlj21hc0u0e0s.jpg" +} +``` + 需要设置的数据路径是 data + 对于下面这段Json: -{"code":0,"msg":"","quota":8,"quota_min_ttl":7029,"count":1,"data":[{"pid":61732396,"p":0,"uid":946272,"title":"カンナ","author":"Aile\/エル","url":"https:\/\/i.pixiv.cat\/img-original\/img\/2017\/03\/04\/00\/00\/01\/61732396_p0.png","r18":false,"width":583,"height":650,"tags":["カンナカムイ(小林さんちのメイドラゴン)","康娜卡姆依(小林家的龙女仆)","カンナ","康娜","カンナカムイ","康娜卡姆依","小林さんちのメイドラゴン","小林家的龙女仆","尻神様","尻神样","竜娘","龙娘","マジやばくね","that's wicked","高品質パンツ","高品质内裤","魅惑のふともも","魅惑的大腿"]}]} + +```json +{ + "code": 0, + "msg": "", + "quota": 8, + "quota_min_ttl": 7029, + "count": 1, + "data": [ + { + "pid": 61732396, + "p": 0, + "uid": 946272, + "title": "カンナ", + "author": "Aile/エル", + "url": "https://i.pixiv.cat/img-original/img/2017/03/04/00/00/01/61732396_p0.png", + "r18": false, + "width": 583, + "height": 650, + "tags": [ + "カンナカムイ(小林さんちのメイドラゴン)", + "康娜卡姆依(小林家的龙女仆)", + "カンナ", + "康娜", + "カンナカムイ", + "康娜卡姆依", + "小林さんちのメイドラゴン", + "小林家的龙女仆", + "尻神様", + "尻神样", + "竜娘", + "龙娘", + "マジやばくね", + "that's wicked", + "高品質パンツ", + "高品质内裤", + "魅惑のふともも", + "魅惑的大腿" + ] + } + ] +} +``` + 需要设置的数据路径是 data\0\url 删除一个源 +``` /sources remove <名称> +``` 刷新源列表 + 对源进行操作时不会立即生效,使用settings reload会导致设置文件被覆盖 -可以使用这个方法来在修改图源列表后刷新图源 + +可以使用这个方法来在修改图源列表后刷新图源 + +``` /sources refresh ``` + 另外,你也可以使用脚本手动完成获取图片的逻辑。至于如何配置使用,请研究ImageSource.kt(懒得写指令了23333) + #### 功能名称表 + ```Kotlin val allowedModuleName = listOf( "ImageSender", @@ -109,44 +213,63 @@ val allowedModuleName = listOf( "Help" ) ``` + ### Pixiv -```Bash + +```text 查看画作信息和预览图 /pixiv illust <画作ID> 查看指定画作的指定P的大图 /pixiv illust <画作ID> <图片索引> ``` + R18画作将不会发送图片 + ### 本地图片 -```Bash + +```text /sendImage <图片数目> ``` + ### Ping -```Bash + +```text /ping 后面和操作系统一样 ``` + ### IP获取 -```Bash + +```text /ipList <网址> /ipList4 <网址> /ipList6 <网址> ``` + ### 黑名单 -```Bash + +```text /darklist add <群号或this> /darklist remove <群号或this> /darklist list <页码,可选,每页15个> ``` + ## Console指令表 + 咕了,还没写呢 + ## 设置Pixiv代理 + * `proxyEnabled` 配置是否启用代理 * `proxyType` 配置代理类型,必须是socks或者http其中一种,填写其它默认socks * `proxyAddress` 配置代理地址,如`127.0.0.1` -* `proxyPort` 配置代理端口,如`1080` -这些代理设置仅用于Pixiv网路连接,不作它用 +* `proxyPort` 配置代理端口,如`1080` + +这些代理设置仅用于Pixiv网路连接 + 实现上使用了Java的Proxy类 + ## 示例PluginConfig.yml + ```yml managers: - 1145141919 diff --git a/src/main/kotlin/io/github/rosemoe/miraiPlugin/gifmaker/Color.kt b/src/main/kotlin/io/github/rosemoe/miraiPlugin/gifmaker/Color.kt deleted file mode 100644 index e3b7ed6..0000000 --- a/src/main/kotlin/io/github/rosemoe/miraiPlugin/gifmaker/Color.kt +++ /dev/null @@ -1,47 +0,0 @@ -/* - * RosemoeBotPlugin - * Copyright (C) 2020-2021 Rosemoe - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published - * by the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -package io.github.rosemoe.miraiPlugin.gifmaker - -object Color { - const val TRANSPARENT = 0 - - /** - * Return the red component of a color int. This is the same as saying - * (color >> 16) & 0xFF - */ - fun red(color: Int): Int { - return color shr 16 and 0xFF - } - - /** - * Return the green component of a color int. This is the same as saying - * (color >> 8) & 0xFF - */ - fun green(color: Int): Int { - return color shr 8 and 0xFF - } - - /** - * Return the blue component of a color int. This is the same as saying - * color & 0xFF - */ - fun blue(color: Int): Int { - return color and 0xFF - } -} \ No newline at end of file diff --git a/src/main/kotlin/io/github/rosemoe/miraiPlugin/gifmaker/GifEncoder.kt b/src/main/kotlin/io/github/rosemoe/miraiPlugin/gifmaker/GifEncoder.kt deleted file mode 100644 index 24539d2..0000000 --- a/src/main/kotlin/io/github/rosemoe/miraiPlugin/gifmaker/GifEncoder.kt +++ /dev/null @@ -1,535 +0,0 @@ -/* - * RosemoeBotPlugin - * Copyright (C) 2020-2021 Rosemoe - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published - * by the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -package io.github.rosemoe.miraiPlugin.gifmaker - -import java.awt.image.BufferedImage -import java.io.BufferedOutputStream -import java.io.FileOutputStream -import java.io.IOException -import java.io.OutputStream -import kotlin.math.roundToInt - -/* -import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.Color; -*/ /** - * Class AnimatedGifEncoder - Encodes a GIF file consisting of one or more - * frames. - * - * - *
- * Example:
- * AnimatedGifEncoder e = new AnimatedGifEncoder();
- * e.start(outputFileName);
- * e.setDelay(1000);   // 1 frame per sec
- * e.addFrame(image1);
- * e.addFrame(image2);
- * e.finish();
-
* - * - * - * No copyright asserted on the source code of this class. May be used for any - * purpose, however, refer to the Unisys LZW patent for restrictions on use of - * the associated LZWEncoder class. Please forward any corrections to - * kweiner@fmsware.com. - * - * @author Kevin Weiner, FM Software - * @version 1.03 November 2003 - */ -open class GifEncoder { - private var width // image size - = 0 - private var height = 0 - private var transparent: Int? = null // transparent color if given - private var hasTransparentPixels = false - private var transIndex // transparent index in color table - = 0 - var repeat = -1 // no repeat - /** - * Sets the number of times the set of GIF frames should be played. Default is - * 1; 0 means play indefinitely. Must be invoked before the first image is - * added. - * - * @param iter int number of iterations. - */ - set(iter) { - if (iter >= 0) { - field = iter - } - } - var delay = 0 // frame delay (hundredths) - /** - * Sets the delay time between each frame, or changes it for subsequent frames - * (applies to last frame added). - * - * @param ms int delay time in milliseconds - */ - set(ms) { - field = Math.round(ms / 10.0f) - } - private var started = false // ready to output frames - private var out: OutputStream? = null - private var image // current frame - : BufferedImage? = null - private var pixels // BGR byte array from frame - : ByteArray? = null - private var indexedPixels // converted frame indexed to palette - : ByteArray? = null - private var colorDepth // number of bit planes - = 0 - private var colorTab // RGB palette - : ByteArray? = null - private var usedEntry = BooleanArray(256) // active palette entries - protected var palSize = 7 // color table size (bits-1) - var dispose = -1 // disposal code (-1 = use default) - /** - * Sets the GIF frame disposal code for the last added frame and any - * subsequent frames. Default is 0 if no transparent color has been set, - * otherwise 2. - * - * @param code int disposal code. - */ - set(code) { - if (code >= 0) { - field = code - } - } - private var closeStream = false // close stream when finished - private var firstFrame = true - private var sizeSet = false // if false, get size from first frame - private var sample = 10 // default sample interval for quantizer - - - /** - * Sets the transparent color for the last added frame and any subsequent - * frames. Since all colors are subject to modification in the quantization - * process, the color in the final palette for each frame closest to the given - * color becomes the transparent color for that frame. May be set to null to - * indicate no transparent color. - * - * @param color Color to be treated as transparent on display. - */ - fun setTransparent(color: Int) { - transparent = color - } - - /** - * Adds next GIF frame. The frame is not written immediately, but is actually - * deferred until the next frame is received so that timing data can be - * inserted. Invoking `finish()` flushes all frames. If - * `setSize` was not invoked, the size of the first image is used - * for all subsequent frames. - * - * @param im BufferedImage containing frame to write. - * @return true if successful. - */ - fun addFrame(im: BufferedImage?): Boolean { - if (im == null || !started) { - return false - } - var ok = true - try { - if (!sizeSet) { - // use first frame's size - setSize(im.width, im.height) - } - image = im - imagePixels // convert to correct format if necessary - analyzePixels() // build color table & map pixels - if (firstFrame) { - writeLSD() // logical screen descriptior - writePalette() // global color table - if (repeat >= 0) { - // use NS app extension to indicate reps - writeNetscapeExt() - } - } - writeGraphicCtrlExt() // write graphic control extension - writeImageDesc() // image descriptor - if (!firstFrame) { - writePalette() // local color table - } - writePixels() // encode and write pixel data - firstFrame = false - } catch (e: IOException) { - ok = false - } - return ok - } - - /** - * Flushes any pending data and closes output file. If writing to an - * OutputStream, the stream is not closed. - */ - fun finish(): Boolean { - if (!started) return false - var ok = true - started = false - try { - out!!.write(0x3b) // gif trailer - out!!.flush() - if (closeStream) { - out!!.close() - } - } catch (e: IOException) { - ok = false - } - - // reset for subsequent use - transIndex = 0 - out = null - image = null - pixels = null - indexedPixels = null - colorTab = null - closeStream = false - firstFrame = true - return ok - } - - /** - * Sets frame rate in frames per second. Equivalent to - * `setDelay(1000/fps)`. - * - * @param fps float frame rate (frames per second) - */ - fun setFrameRate(fps: Float) { - if (fps != 0f) { - delay = (100f / fps).roundToInt() - } - } - - /** - * Sets quality of color quantization (conversion of images to the maximum 256 - * colors allowed by the GIF specification). Lower values (minimum = 1) - * produce better colors, but slow processing significantly. 10 is the - * default, and produces good color mapping at reasonable speeds. Values - * greater than 20 do not yield significant improvements in speed. - * - * @param quality int greater than 0. - */ - fun setQuality(quality: Int) { - sample = if(quality < 1) 1 else quality - } - - /** - * Sets the GIF frame size. The default size is the size of the first frame - * added if this method is not invoked. - * - * @param w int frame width. - * @param h int frame width. - */ - fun setSize(w: Int, h: Int) { - if (started && !firstFrame) return - width = w - height = h - if (width < 1) width = 320 - if (height < 1) height = 240 - sizeSet = true - } - - /** - * Initiates GIF file creation on the given stream. The stream is not closed - * automatically. - * - * @param os OutputStream on which GIF images are written. - * @return false if initial write failed. - */ - fun start(os: OutputStream?): Boolean { - if (os == null) return false - var ok = true - closeStream = false - out = os - try { - writeString("GIF89a") // header - } catch (e: IOException) { - ok = false - } - return ok.also { started = it } - } - - /** - * Initiates writing of a GIF file with the specified name. - * - * @param file String containing output file name. - * @return false if open or initial write failed. - */ - fun start(file: String): Boolean { - var ok:Boolean - try { - out = BufferedOutputStream(FileOutputStream(file)) - ok = start(out) - closeStream = true - } catch (e: IOException) { - e.printStackTrace() - ok = false - } - return ok.also { started = it } - } - - /** - * Analyzes image colors and creates color map. - */ - private fun analyzePixels() { - val len = pixels!!.size - val nPix = len / 3 - indexedPixels = ByteArray(nPix) - val nq = NeuQuant(pixels!!, len, sample) - // initialize quantizer - colorTab = nq.process() // create reduced palette - // convert map from BGR to RGB - run { - var i = 0 - while (i < colorTab!!.size) { - val temp = colorTab!![i] - colorTab!![i] = colorTab!![i + 2] - colorTab!![i + 2] = temp - usedEntry[i / 3] = false - i += 3 - } - } - // map image pixels to new palette - var k = 0 - for (i in 0 until nPix) { - val index = nq.map(pixels!![k++].toInt() and 0xff, pixels!![k++].toInt() and 0xff, pixels!![k++].toInt() and 0xff) - usedEntry[index] = true - indexedPixels!![i] = index.toByte() - } - pixels = null - colorDepth = 8 - palSize = 7 - // get closest match to transparent color if specified - if (transparent != null) { - transIndex = findClosest(transparent!!) - } else if (hasTransparentPixels) { - transIndex = findClosest(Color.TRANSPARENT) - } - } - - /** - * Returns index of palette color closest to c - */ - private fun findClosest(color: Int): Int { - if (colorTab == null) return -1 - val r: Int = Color.red(color) - val g: Int = Color.green(color) - val b: Int = Color.blue(color) - var minpos = 0 - var dmin = 256 * 256 * 256 - val len = colorTab!!.size - var i = 0 - while (i < len) { - val dr: Int = r - (colorTab!![i++].toInt() and 0xff) - val dg: Int = g - (colorTab!![i++].toInt() and 0xff) - val db: Int = b - (colorTab!![i].toInt() and 0xff) - val d = dr * dr + dg * dg + db * db - val index = i / 3 - if (usedEntry[index] && d < dmin) { - dmin = d - minpos = index - } - i++ - } - return minpos - }// create new image with right size/format - //image.getPixels(pixelsInt, 0, w, 0, 0, w, h); - - // The algorithm requires 3 bytes per pixel as RGB. - // Assume images with greater where more than n% of the pixels are transparent actually have transparency. - // See issue #214. - /** - * Extracts image pixels into byte array "pixels" - */ - private val imagePixels: Unit - get() { - val w = image!!.width - val h = image!!.height - if (w != width || h != height) { - // create new image with right size/format - val temp = BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB) - temp.createGraphics() - .drawImage(image, 0, 0, width, height, null) - image = temp - } - val pixelsInt = IntArray(w * h) - image!!.getRGB(0, 0, w, h, pixelsInt, 0, w) - //image.getPixels(pixelsInt, 0, w, 0, 0, w, h); - - // The algorithm requires 3 bytes per pixel as RGB. - pixels = ByteArray(pixelsInt.size * 3) - var pixelsIndex = 0 - hasTransparentPixels = false - var totalTransparentPixels = 0 - for (pixel in pixelsInt) { - if (pixel == Color.TRANSPARENT) { - totalTransparentPixels++ - } - pixels!![pixelsIndex++] = (pixel and 0xFF).toByte() - pixels!![pixelsIndex++] = (pixel shr 8 and 0xFF).toByte() - pixels!![pixelsIndex++] = (pixel shr 16 and 0xFF).toByte() - } - val transparentPercentage = 100 * totalTransparentPixels / pixelsInt.size.toDouble() - // Assume images with greater where more than n% of the pixels are transparent actually have transparency. - // See issue #214. - hasTransparentPixels = transparentPercentage > MIN_TRANSPARENT_PERCENTAGE - //println("got pixels for frame with $transparentPercentage% transparent pixels") - } - - /** - * Writes Graphic Control Extension - */ - @Throws(IOException::class) - protected fun writeGraphicCtrlExt() { - out!!.write(0x21) // extension introducer - out!!.write(0xf9) // GCE label - out!!.write(4) // data block size - val transp: Int - var disp: Int - if (transparent == null && !hasTransparentPixels) { - transp = 0 - disp = 0 // dispose = no action - } else { - transp = 1 - disp = 2 // force clear if using transparent color - } - if (dispose >= 0) { - disp = dispose and 7 // user override - } - disp = disp shl 2 - - // packed fields - out!!.write( - 0 or // 1:3 reserved - disp or // 4:6 disposal - 0 or // 7 user input - 0 = none - transp - ) // 8 transparency flag - writeShort(delay) // delay x 1/100 sec - out!!.write(transIndex) // transparent color index - out!!.write(0) // block terminator - } - - /** - * Writes Image Descriptor - */ - @Throws(IOException::class) - protected fun writeImageDesc() { - out!!.write(0x2c) // image separator - writeShort(0) // image position x,y = 0,0 - writeShort(0) - writeShort(width) // image size - writeShort(height) - // packed fields - if (firstFrame) { - // no LCT - GCT is used for first (or only) frame - out!!.write(0) - } else { - // specify normal LCT - out!!.write( - 0x80 or // 1 local color table 1=yes - 0 or // 2 interlace - 0=no - 0 or // 3 sorted - 0=no - 0 or // 4-5 reserved - palSize - ) // 6-8 size of color table - } - } - - /** - * Writes Logical Screen Descriptor - */ - @Throws(IOException::class) - protected fun writeLSD() { - // logical screen size - writeShort(width) - writeShort(height) - // packed fields - out!!.write( - 0x80 or // 1 : global color table flag = 1 (gct used) - 0x70 or // 2-4 : color resolution = 7 - 0x00 or // 5 : gct sort flag = 0 - palSize - ) // 6-8 : gct size - out!!.write(0) // background color index - out!!.write(0) // pixel aspect ratio - assume 1:1 - } - - /** - * Writes Netscape application extension to define repeat count. - */ - @Throws(IOException::class) - protected fun writeNetscapeExt() { - out!!.write(0x21) // extension introducer - out!!.write(0xff) // app extension label - out!!.write(11) // block size - writeString("NETSCAPE" + "2.0") // app id + auth code - out!!.write(3) // sub-block size - out!!.write(1) // loop sub-block id - writeShort(repeat) // loop count (extra iterations, 0=repeat forever) - out!!.write(0) // block terminator - } - - /** - * Writes color table - */ - @Throws(IOException::class) - protected fun writePalette() { - out!!.write(colorTab!!, 0, colorTab!!.size) - val n = 3 * 256 - colorTab!!.size - for (i in 0 until n) { - out!!.write(0) - } - } - - /** - * Encodes and writes pixel data - */ - @Throws(IOException::class) - private fun writePixels() { - val encoder = LZWEncoder(width, height, indexedPixels!!, colorDepth) - encoder.encode(out!!) - } - - /** - * Write 16-bit value to output stream, LSB first - */ - @Throws(IOException::class) - private fun writeShort(value: Int) { - out!!.write(value and 0xff) - out!!.write(value shr 8 and 0xff) - } - - /** - * Writes string to output stream - */ - @Throws(IOException::class) - protected fun writeString(s: String) { - for (element in s) { - out!!.write(element.code) - } - } - - companion object { - private const val TAG = "AnimatedGifEncoder" - - // The minimum % of an images pixels that must be transparent for us to set a transparent index automatically. - private const val MIN_TRANSPARENT_PERCENTAGE = 4.0 - } -} \ No newline at end of file diff --git a/src/main/kotlin/io/github/rosemoe/miraiPlugin/gifmaker/LZWEncoder.kt b/src/main/kotlin/io/github/rosemoe/miraiPlugin/gifmaker/LZWEncoder.kt deleted file mode 100644 index b6bb22e..0000000 --- a/src/main/kotlin/io/github/rosemoe/miraiPlugin/gifmaker/LZWEncoder.kt +++ /dev/null @@ -1,264 +0,0 @@ -/* - * RosemoeBotPlugin - * Copyright (C) 2020-2021 Rosemoe - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published - * by the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -package io.github.rosemoe.miraiPlugin.gifmaker - -import java.io.IOException -import java.io.OutputStream - -class LZWEncoder(private val imgW: Int, private val imgH: Int, private val pixAry: ByteArray, color_depth: Int) { - private val initCodeSize: Int - private var remaining = 0 - private var curPixel = 0 - - // GIF Image compression - modified 'compress' - // - // Based on: compress.c - File compression ala IEEE Computer, June 1984. - // - // By Authors: Spencer W. Thomas (decvax!harpo!utah-cs!utah-gr!thomas) - // Jim McKie (decvax!mcvax!jim) - // Steve Davies (decvax!vax135!petsd!peora!srd) - // Ken Turkowski (decvax!decwrl!turtlevax!ken) - // James A. Woods (decvax!ihnp4!ames!jaw) - // Joe Orost (decvax!vax135!petsd!joe) - var n_bits // number of bits/code - = 0 - var maxbits = BITS // user settable max # bits/code - var maxcode // maximum code, given n_bits - = 0 - var maxmaxcode = 1 shl BITS // should NEVER generate this code - var htab = IntArray(HSIZE) - var codetab = IntArray(HSIZE) - var hsize = HSIZE // for dynamic table sizing - var free_ent = 0 // first unused entry - - // block compression parameters -- after all codes are used up, - // and compression rate changes, start over. - var clear_flg = false - - // Algorithm: use open addressing double hashing (no chaining) on the - // prefix code / next character combination. We do a variant of Knuth's - // algorithm D (vol. 3, sec. 6.4) along with G. Knott's relatively-prime - // secondary probe. Here, the modular division first probe is gives way - // to a faster exclusive-or manipulation. Also do block compression with - // an adaptive reset, whereby the code table is cleared when the compression - // ratio decreases, but after the table fills. The variable-length output - // codes are re-sized at this point, and a special CLEAR code is generated - // for the decompressor. Late addition: construct the table according to - // file size for noticeable speed improvement on small files. Please direct - // questions about this implementation to ames!jaw. - var g_init_bits = 0 - var ClearCode = 0 - var EOFCode = 0 - - // output - // - // Output the given code. - // Inputs: - // code: A n_bits-bit integer. If == -1, then EOF. This assumes - // that n_bits =< wordsize - 1. - // Outputs: - // Outputs code to the file. - // Assumptions: - // Chars are 8 bits long. - // Algorithm: - // Maintain a BITS character long buffer (so that 8 codes will - // fit in it exactly). Use the VAX insv instruction to insert each - // code in turn. When the buffer fills up empty it and start over. - var cur_accum = 0 - var cur_bits = 0 - var masks = intArrayOf( - 0x0000, 0x0001, 0x0003, 0x0007, 0x000F, 0x001F, 0x003F, 0x007F, 0x00FF, 0x01FF, - 0x03FF, 0x07FF, 0x0FFF, 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF - ) - - // Number of characters so far in this 'packet' - var a_count = 0 - - // Define the storage for the packet accumulator - var accum = ByteArray(256) - - // Add a character to the end of the current packet, and if it is 254 - // characters, flush the packet to disk. - @Throws(IOException::class) - fun char_out(c: Byte, outs: OutputStream) { - accum[a_count++] = c - if (a_count >= 254) flush_char(outs) - } - - // Clear out the hash table - // table clear for block compress - @Throws(IOException::class) - fun cl_block(outs: OutputStream) { - cl_hash(hsize) - free_ent = ClearCode + 2 - clear_flg = true - output(ClearCode, outs) - } - - // reset code table - fun cl_hash(hsize: Int) { - for (i in 0 until hsize) htab[i] = -1 - } - - @Throws(IOException::class) - fun compress(init_bits: Int, outs: OutputStream) { - var fcode: Int - var i /* = 0 */: Int - var c: Int - var ent: Int - var disp: Int - val hsize_reg: Int - var hshift: Int - - // Set up the globals: g_init_bits - initial number of bits - g_init_bits = init_bits - - // Set up the necessary values - clear_flg = false - n_bits = g_init_bits - maxcode = MAXCODE(n_bits) - ClearCode = 1 shl init_bits - 1 - EOFCode = ClearCode + 1 - free_ent = ClearCode + 2 - a_count = 0 // clear packet - ent = nextPixel() - hshift = 0 - fcode = hsize - while (fcode < 65536) { - ++hshift - fcode *= 2 - } - hshift = 8 - hshift // set hash code range bound - hsize_reg = hsize - cl_hash(hsize_reg) // clear hash table - output(ClearCode, outs) - outer_loop@ while (nextPixel().also { c = it } != EOF) { - fcode = (c shl maxbits) + ent - i = c shl hshift xor ent // xor hashing - if (htab[i] == fcode) { - ent = codetab[i] - continue - } else if (htab[i] >= 0) // non-empty slot - { - disp = hsize_reg - i // secondary hash (after G. Knott) - if (i == 0) disp = 1 - do { - if (disp.let { i -= it; i } < 0) i += hsize_reg - if (htab[i] == fcode) { - ent = codetab[i] - continue@outer_loop - } - } while (htab[i] >= 0) - } - output(ent, outs) - ent = c - if (free_ent < maxmaxcode) { - codetab[i] = free_ent++ // code -> hashtable - htab[i] = fcode - } else cl_block(outs) - } - // Put out the final code. - output(ent, outs) - output(EOFCode, outs) - } - - // ---------------------------------------------------------------------------- - @Throws(IOException::class) - fun encode(os: OutputStream) { - os.write(initCodeSize) // write "initial code size" byte - remaining = imgW * imgH // reset navigation variables - curPixel = 0 - compress(initCodeSize + 1, os) // compress and write the pixel data - os.write(0) // write block terminator - } - - // Flush the packet to disk, and reset the accumulator - @Throws(IOException::class) - fun flush_char(outs: OutputStream) { - if (a_count > 0) { - outs.write(a_count) - outs.write(accum, 0, a_count) - a_count = 0 - } - } - - fun MAXCODE(n_bits: Int): Int { - return (1 shl n_bits) - 1 - } - - // ---------------------------------------------------------------------------- - // Return the next pixel from the image - // ---------------------------------------------------------------------------- - private fun nextPixel(): Int { - if (remaining == 0) return EOF - --remaining - val pix = pixAry[curPixel++] - return pix.toInt() and 0xff - } - - @Throws(IOException::class) - fun output(code: Int, outs: OutputStream) { - cur_accum = cur_accum and masks[cur_bits] - cur_accum = if (cur_bits > 0) cur_accum or (code shl cur_bits) else code - cur_bits += n_bits - while (cur_bits >= 8) { - char_out((cur_accum and 0xff).toByte(), outs) - cur_accum = cur_accum shr 8 - cur_bits -= 8 - } - - // If the next entry is going to be too big for the code size, - // then increase it, if possible. - if (free_ent > maxcode || clear_flg) { - if (clear_flg) { - maxcode = MAXCODE(g_init_bits.also { n_bits = it }) - clear_flg = false - } else { - ++n_bits - maxcode = if (n_bits == maxbits) maxmaxcode else MAXCODE(n_bits) - } - } - if (code == EOFCode) { - // At EOF, write the rest of the buffer. - while (cur_bits > 0) { - char_out((cur_accum and 0xff).toByte(), outs) - cur_accum = cur_accum shr 8 - cur_bits -= 8 - } - flush_char(outs) - } - } - - companion object { - private const val EOF = -1 - - // GIFCOMPR.C - GIF Image compression routines - // - // Lempel-Ziv compression based on 'compress'. GIF modifications by - // David Rowley (mgardi@watdcsu.waterloo.edu) - // General DEFINEs - const val BITS = 12 - const val HSIZE = 5003 // 80% occupancy - } - - // ---------------------------------------------------------------------------- - init { - initCodeSize = Math.max(2, color_depth) - } -} \ No newline at end of file diff --git a/src/main/kotlin/io/github/rosemoe/miraiPlugin/gifmaker/NeuQuant.kt b/src/main/kotlin/io/github/rosemoe/miraiPlugin/gifmaker/NeuQuant.kt deleted file mode 100644 index 866a128..0000000 --- a/src/main/kotlin/io/github/rosemoe/miraiPlugin/gifmaker/NeuQuant.kt +++ /dev/null @@ -1,471 +0,0 @@ -/* - * RosemoeBotPlugin - * Copyright (C) 2020-2021 Rosemoe - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published - * by the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -package io.github.rosemoe.miraiPlugin.gifmaker - -/* - * NeuQuant Neural-Net Quantization Algorithm - * ------------------------------------------ - * - * Copyright (c) 1994 Anthony Dekker - * - * NEUQUANT Neural-Net quantization algorithm by Anthony Dekker, 1994. See - * "Kohonen neural networks for optimal colour quantization" in "Network: - * Computation in Neural Systems" Vol. 5 (1994) pp 351-367. for a discussion of - * the algorithm. - * - * Any party obtaining a copy of these files from the author, directly or - * indirectly, is granted, free of charge, a full and unrestricted irrevocable, - * world-wide, paid up, royalty-free, nonexclusive right and license to deal in - * this software and documentation files (the "Software"), including without - * limitation the rights to use, copy, modify, merge, publish, distribute, - * sublicense, and/or sell copies of the Software, and to permit persons who - * receive copies from any such party to do so, with the only requirement being - * that this copyright notice remain intact. - */ // Ported to Java 12/00 K Weiner -internal class NeuQuant(thepic: ByteArray, len: Int, sample: Int) { - private var alphadec /* biased by 10 bits */ = 0 - - /* - * Types and Global Variables -------------------------- - */ - private var thepicture /* the input image itself */: ByteArray - private var lengthcount /* lengthcount = H*W*3 */: Int - private var samplefac /* sampling factor 1..30 */: Int - - // typedef int pixel[4]; /* BGRc */ - private var network /* the network itself - [netsize][4] */: Array - private var netindex = IntArray(256) - - /* for network lookup - really 256 */ - private var bias = IntArray(netsize) - - /* bias and freq arrays for learning */ - private var freq = IntArray(netsize) - private var radpower = IntArray(initrad) - private fun colorMap(): ByteArray { - val map = ByteArray(3 * netsize) - val index = IntArray(netsize) - for (i in 0 until netsize) index[network[i]!![3]] = i - var k = 0 - for (i in 0 until netsize) { - val j = index[i] - map[k++] = network[j]!![0].toByte() - map[k++] = network[j]!![1].toByte() - map[k++] = network[j]!![2].toByte() - } - return map - } - - /* - * Insertion sort of network and building of netindex[0..255] (to do after - * unbias) - * ------------------------------------------------------------------------------- - */ - private fun inxbuild() { - var j: Int - var smallpos: Int - var smallval: Int - var p: IntArray? - var q: IntArray? - var previouscol: Int - var startpos: Int - previouscol = 0 - startpos = 0 - var i = 0 - while (i < netsize) { - p = network[i] - smallpos = i - smallval = p!![1] /* index on g */ - /* find smallest in i..netsize-1 */j = i + 1 - while (j < netsize) { - q = network[j] - if (q!![1] < smallval) { /* index on g */ - smallpos = j - smallval = q[1] /* index on g */ - } - j++ - } - q = network[smallpos] - /* swap p (i) and q (smallpos) entries */if (i != smallpos) { - j = q!![0] - q[0] = p[0] - p[0] = j - j = q[1] - q[1] = p[1] - p[1] = j - j = q[2] - q[2] = p[2] - p[2] = j - j = q[3] - q[3] = p[3] - p[3] = j - } - /* smallval entry is now in position i */if (smallval != previouscol) { - netindex[previouscol] = startpos + i shr 1 - j = previouscol + 1 - while (j < smallval) { - netindex[j] = i - j++ - } - previouscol = smallval - startpos = i - } - i++ - } - netindex[previouscol] = startpos + maxnetpos shr 1 - j = previouscol + 1 - while (j < 256) { - netindex[j] = maxnetpos /* really 256 */ - j++ - } - } - - /* - * Main Learning Loop ------------------ - */ - private fun learn() { - var j: Int - var b: Int - var g: Int - var r: Int - var rad: Int - var delta: Int - if (lengthcount < minpicturebytes) samplefac = 1 - alphadec = 30 + (samplefac - 1) / 3 - val p = thepicture - var pix = 0 - val lim = lengthcount - val samplepixels = lengthcount / (3 * samplefac) - delta = samplepixels / ncycles - var alpha = initalpha - var radius = initradius - rad = radius shr radiusbiasshift - if (rad <= 1) rad = 0 - var i = 0 - while (i < rad) { - radpower[i] = alpha * ((rad * rad - i * i) * radbias / (rad * rad)) - i++ - } - - // fprintf(stderr,"beginning 1D learning: initial radius=%d\n", rad); - val step = if (lengthcount < minpicturebytes) 3 else if (lengthcount % prime1 != 0) 3 * prime1 else { - if (lengthcount % prime2 != 0) 3 * prime2 else { - if (lengthcount % prime3 != 0) 3 * prime3 else 3 * prime4 - } - } - i = 0 - while (i < samplepixels) { - b = (p[pix + 0].toInt() and 0xff) shl netbiasshift - g = (p[pix + 1].toInt() and 0xff) shl netbiasshift - r = (p[pix + 2].toInt() and 0xff) shl netbiasshift - j = contest(b, g, r) - altersingle(alpha, j, b, g, r) - if (rad != 0) alterneigh(rad, j, b, g, r) /* alter neighbours */ - pix += step - if (pix >= lim) pix -= lengthcount - i++ - if (delta == 0) delta = 1 - if (i % delta == 0) { - alpha -= alpha / alphadec - radius -= radius / radiusdec - rad = radius shr radiusbiasshift - if (rad <= 1) rad = 0 - j = 0 - while (j < rad) { - radpower[j] = alpha * ((rad * rad - j * j) * radbias / (rad * rad)) - j++ - } - } - } - // fprintf(stderr,"finished 1D learning: final alpha=%f - // !\n",((float)alpha)/initalpha); - } - - /* - * Search for BGR values 0..255 (after net is unbiased) and return colour - * index - * ---------------------------------------------------------------------------- - */ - fun map(b: Int, g: Int, r: Int): Int { - var i: Int - var j: Int - var dist: Int - var a: Int - var bestd: Int - var p: IntArray? - var best: Int - bestd = 1000 /* biggest possible dist is 256*3 */ - best = -1 - i = netindex[g] /* index on g */ - j = i - 1 /* start at netindex[g] and work outwards */ - while (i < netsize || j >= 0) { - if (i < netsize) { - p = network[i] - dist = p!![1] - g /* inx key */ - if (dist >= bestd) i = netsize /* stop iter */ else { - i++ - if (dist < 0) dist = -dist - a = p[0] - b - if (a < 0) a = -a - dist += a - if (dist < bestd) { - a = p[2] - r - if (a < 0) a = -a - dist += a - if (dist < bestd) { - bestd = dist - best = p[3] - } - } - } - } - if (j >= 0) { - p = network[j] - dist = g - p!![1] /* inx key - reverse dif */ - if (dist >= bestd) j = -1 /* stop iter */ else { - j-- - if (dist < 0) dist = -dist - a = p[0] - b - if (a < 0) a = -a - dist += a - if (dist < bestd) { - a = p[2] - r - if (a < 0) a = -a - dist += a - if (dist < bestd) { - bestd = dist - best = p[3] - } - } - } - } - } - return best - } - - fun process(): ByteArray { - learn() - unbiasnet() - inxbuild() - return colorMap() - } - - /* - * Unbias network to give byte values 0..255 and record position i to prepare - * for sort - * ----------------------------------------------------------------------------------- - */ - private fun unbiasnet() { - //var j: Int - var i = 0 - while (i < netsize) { - network[i]!![0] = network[i]!![0] shr netbiasshift - network[i]!![1] = network[i]!![1] shr netbiasshift - network[i]!![2] = network[i]!![2] shr netbiasshift - network[i]!![3] = i /* record colour no */ - i++ - } - } - - /* - * Move adjacent neurons by precomputed alpha*(1-((i-j)^2/[r]^2)) in - * radpower[|i-j|] - * --------------------------------------------------------------------------------- - */ - private fun alterneigh(rad: Int, i: Int, b: Int, g: Int, r: Int) { - var lo: Int - var hi: Int - var a: Int - var p: IntArray? - lo = i - rad - if (lo < -1) lo = -1 - hi = i + rad - if (hi > netsize) hi = netsize - var j = i + 1 - var k = i - 1 - var m = 1 - while (j < hi || k > lo) { - a = radpower[m++] - if (j < hi) { - p = network[j++] - try { - p!![0] -= a * (p!![0] - b) / alpharadbias - p[1] -= a * (p[1] - g) / alpharadbias - p[2] -= a * (p[2] - r) / alpharadbias - } catch (e: Exception) { - } // prevents 1.3 miscompilation - } - if (k > lo) { - p = network[k--] - try { - p!![0] -= a * (p!![0] - b) / alpharadbias - p[1] -= a * (p[1] - g) / alpharadbias - p[2] -= a * (p[2] - r) / alpharadbias - } catch (e: Exception) { - } - } - } - } - - /* - * Move neuron i towards biased (b,g,r) by factor alpha - * ---------------------------------------------------- - */ - private fun altersingle(alpha: Int, i: Int, b: Int, g: Int, r: Int) { - - /* alter hit neuron */ - val n = network[i] - n!![0] -= alpha * (n!![0] - b) / initalpha - n[1] -= alpha * (n[1] - g) / initalpha - n[2] -= alpha * (n[2] - r) / initalpha - } - - /* - * Search for biased BGR values ---------------------------- - */ - private fun contest(b: Int, g: Int, r: Int): Int { - - /* finds closest neuron (min dist) and updates freq */ - /* finds best neuron (min dist-bias) and returns position */ - /* for frequently chosen neurons, freq[i] is high and bias[i] is negative */ - /* bias[i] = gamma*((1/netsize)-freq[i]) */ - var dist: Int - var a: Int - var biasdist: Int - var betafreq: Int - var bestpos: Int - var bestbiaspos: Int - var bestd: Int - var bestbiasd: Int - var n: IntArray? - bestd = (1 shl 31).inv() - bestbiasd = bestd - bestpos = -1 - bestbiaspos = bestpos - var i = 0 - while (i < netsize) { - n = network[i] - dist = n!![0] - b - if (dist < 0) dist = -dist - a = n[1] - g - if (a < 0) a = -a - dist += a - a = n[2] - r - if (a < 0) a = -a - dist += a - if (dist < bestd) { - bestd = dist - bestpos = i - } - biasdist = dist - (bias[i] shr intbiasshift - netbiasshift) - if (biasdist < bestbiasd) { - bestbiasd = biasdist - bestbiaspos = i - } - betafreq = freq[i] shr betashift - freq[i] -= betafreq - bias[i] += betafreq shl gammashift - i++ - } - freq[bestpos] += beta - bias[bestpos] -= betagamma - return bestbiaspos - } - - companion object { - private const val netsize = 256 /* number of colours used */ - - /* four primes near 500 - assume no image has a length so large */ /* that it is divisible by all four primes */ - private const val prime1 = 499 - private const val prime2 = 491 - private const val prime3 = 487 - private const val prime4 = 503 - private const val minpicturebytes = 3 * prime4 - - /* minimum size for input image */ /* - * Program Skeleton ---------------- [select samplefac in range 1..30] [read - * image from input file] pic = (unsigned char*) malloc(3*width*height); - * initnet(pic,3*width*height,samplefac); learn(); unbiasnet(); [write output - * image header, using writecolourmap(f)] inxbuild(); write output image using - * inxsearch(b,g,r) - */ - /* - * Network Definitions ------------------- - */ - private const val maxnetpos = netsize - 1 - private const val netbiasshift = 4 /* bias for colour values */ - private const val ncycles = 100 /* no. of learning cycles */ - - /* defs for freq and bias */ - private const val intbiasshift = 16 /* bias for fractions */ - private const val intbias = 1 shl intbiasshift - private const val gammashift = 10 /* gamma = 1024 */ - private const val gamma = 1 shl gammashift - private const val betashift = 10 - private const val beta = intbias shr betashift /* beta = 1/1024 */ - private const val betagamma = intbias shl gammashift - betashift - - /* defs for decreasing radius factor */ - private const val initrad = netsize shr 3 /* - * for 256 cols, radius - * starts - */ - private const val radiusbiasshift = 6 /* at 32.0 biased by 6 bits */ - private const val radiusbias = 1 shl radiusbiasshift - private const val initradius = initrad * radiusbias /* - * and - * decreases - * by a - */ - private const val radiusdec = 30 /* factor of 1/30 each cycle */ - - /* defs for decreasing alpha factor */ - private const val alphabiasshift = 10 /* alpha starts at 1.0 */ - private const val initalpha = 1 shl alphabiasshift - - /* radbias and alpharadbias used for radpower calculation */ - private const val radbiasshift = 8 - private const val radbias = 1 shl radbiasshift - private const val alpharadbshift = alphabiasshift + radbiasshift - private const val alpharadbias = 1 shl alpharadbshift - } - - /* radpower for precomputation */ /* - * Initialise network in range (0,0,0) to (255,255,255) and set parameters - * ----------------------------------------------------------------------- - */ - init { - var p: IntArray? - thepicture = thepic - lengthcount = len - samplefac = sample - network = arrayOfNulls(netsize) - var i = 0 - while (i < netsize) { - network[i] = IntArray(4) - p = network[i] - p!![2] = (i shl netbiasshift + 8) / netsize - p!![1] = p!![2] - p!![0] = p!![1] - freq[i] = intbias / netsize /* 1/netsize */ - bias[i] = 0 - i++ - } - } -} \ No newline at end of file diff --git a/src/main/kotlin/io/github/rosemoe/miraiPlugin/modules/PetPet.kt b/src/main/kotlin/io/github/rosemoe/miraiPlugin/modules/PetPet.kt index 801f6ce..2e247b8 100644 --- a/src/main/kotlin/io/github/rosemoe/miraiPlugin/modules/PetPet.kt +++ b/src/main/kotlin/io/github/rosemoe/miraiPlugin/modules/PetPet.kt @@ -18,98 +18,42 @@ package io.github.rosemoe.miraiPlugin -import io.github.rosemoe.miraiPlugin.gifmaker.GifEncoder import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.runInterruptible import net.mamoe.mirai.contact.Group -import java.awt.Color -import java.awt.image.BufferedImage import java.io.File import java.io.FileInputStream import java.io.FileOutputStream import java.io.IOException import java.net.HttpURLConnection import java.net.URL -import javax.imageio.ImageIO -import kotlin.math.max - -private const val OUT_SIZE = 112//hand size -private const val MAX_FRAME = 5 - -private const val squish = 1.25 -private const val scale = 0.875 -private const val spriteY = 20.0 -private const val duration = 16 - -private val frameOffsets = listOf( - mapOf("x" to 0, "y" to 0, "w" to 0, "h" to 0), - mapOf("x" to -4, "y" to 12, "w" to 4, "h" to -12), - mapOf("x" to -12, "y" to 18, "w" to 12, "h" to -18), - mapOf("x" to -8, "y" to 12, "w" to 4, "h" to -12), - mapOf("x" to -4, "y" to 0, "w" to 0, "h" to 0) -) - -private val hands: Array by lazy { - val handImage = ImageIO.read( - getTargetImage( - "https://benisland.neocities.org/petpet/img/sprite.png", - "${RosemoePlugin.dataFolderPath}${File.separator}hand.png" - ) - ) - Array(MAX_FRAME) { - handImage.getSubimage(it * OUT_SIZE, 0, OUT_SIZE, OUT_SIZE) - } -} suspend fun RosemoePlugin.generateGifAndSend(url: String, group: Group, id: Long) { - val outputFile = newFile("${userDirPath(id)}${File.separator}PetPet.gif") + val outputFile = "${userDirPath(id)}${File.separator}PetPet.gif" + var generationSuccess = true runInterruptible(Dispatchers.IO) { - val head = ImageIO.read(FileInputStream(getUserHead(url, id))) - val outputStream = FileOutputStream(outputFile) - GifEncoder().run { - delay = duration - repeat = 0 - setTransparent(Color.TRANSLUCENT) - start(outputStream) - for (i in 0 until MAX_FRAME) { - addFrame(generateFrame(head, i)) + getUserHead(url, id) + val head = "${userDirPath(id)}${File.separator}avatar.jpg" + try { + val process = Runtime.getRuntime().exec(".${File.separator}petpet ${head} ${outputFile} 10", arrayOf("RUST_BACKTRACE=1")) + if (process.waitFor() != 0) { // if errors occured at native + generationSuccess = false + throw Exception(process.getErrorStream().bufferedReader().readText()) // print Rust backtrace } - finish() + } catch (e: Exception) { + e.printStackTrace() + generationSuccess = false } } - group.sendMessage(group.uploadImageResource(outputFile)) + if (generationSuccess) { + group.sendMessage(group.uploadImageResource(File(outputFile))) + } } operator fun Map.minus(x: K): V { return getValue(x) } -private fun getSpriteFrame(i: Int): Map { - val offset = frameOffsets[i] - return mapOf( - "dx" to ((offset - "x") * squish * 0.4).toInt(), - "dy" to (spriteY + (offset - "y") * squish * 0.9).toInt(), - "dw" to ((OUT_SIZE + (offset - "w") * squish) * scale).toInt(), - "dh" to ((OUT_SIZE + (offset - "h") * squish) * scale).toInt() - ) -} - -private fun generateFrame(head: BufferedImage, i: Int): BufferedImage { - val cf = getSpriteFrame(i) - val result = BufferedImage(OUT_SIZE, OUT_SIZE, BufferedImage.TYPE_INT_ARGB) - result.createGraphics().apply { - //color = Color.WHITE - //drawRect(0, 0, OUT_SIZE, OUT_SIZE) - //fillRect(0, 0, OUT_SIZE, OUT_SIZE) - create().apply { - translate(cf - "dx" + 15, cf - "dy" + 15) - drawImage(head, 0, 0, ((cf - "dw") * 0.9).toInt(), ((cf - "dh") * 0.9).toInt(), null) - } - drawImage(hands[i], 0, max(0.0, ((cf - "dy") * 0.75 - max(0.0, spriteY) - 0.5)).toInt(), null, null) - } - return result -} - @Throws(IOException::class) private fun getUserHead(url: String, memberId: Long): File { return getTargetImage(