From c529521774d93faece13d3fba6aaca51d7651fd2 Mon Sep 17 00:00:00 2001 From: Dituon <68615161+Dituon@users.noreply.github.com> Date: Wed, 18 Oct 2023 20:34:54 +0900 Subject: [PATCH] =?UTF-8?q?Feat:=20AvatarModel:=20=E6=96=B0=E5=A2=9E=20Tra?= =?UTF-8?q?nsformOrigin=20&=20=E6=B8=B2=E6=9F=93=E8=BF=87=E7=A8=8B?= =?UTF-8?q?=E5=A4=9A=E7=BA=BF=E7=A8=8B=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- data/xmmt.dituon.petpet/ask/data.json | 29 +- data/xmmt.dituon.petpet/brain/data.json | 2 - data/xmmt.dituon.petpet/cast/data.json | 2 - .../genshin_start/data.json | 15 +- data/xmmt.dituon.petpet/join/data.json | 14 +- data/xmmt.dituon.petpet/kita/data.json | 50 ++- data/xmmt.dituon.petpet/record/data.json | 2 +- data/xmmt.dituon.petpet/roll/data.json | 2 +- data/xmmt.dituon.petpet/twist/data.json | 2 +- .../moe/dituon/petpet/share/AvatarModel.java | 288 ++++++++---------- .../moe/dituon/petpet/share/BaseConfig.kt | 5 + .../dituon/petpet/share/ImageSynthesis.java | 35 ++- .../petpet/share/ImageSynthesisCore.java | 38 +-- 13 files changed, 267 insertions(+), 217 deletions(-) diff --git a/data/xmmt.dituon.petpet/ask/data.json b/data/xmmt.dituon.petpet/ask/data.json index 98d019ff..f1b2a947 100644 --- a/data/xmmt.dituon.petpet/ask/data.json +++ b/data/xmmt.dituon.petpet/ask/data.json @@ -4,12 +4,9 @@ "type":"TO", "posType":"ZOOM", "pos":[-0,100,600,600], - "style":[], "fit":"COVER", - "opacity":1, - "rotate":false, - "round":false, - "avatarOnTop":false}], + "avatarOnTop":false + }], "text":[ { "text":"$txt1[$to]不知道哦。", @@ -17,40 +14,26 @@ "color":"#ffffff", "size":26, "align":"CENTER", - "wrap":"NONE", - "style":"PLAIN", - "strokeColor":null, - "strokeSize":1 + "style":"PLAIN" },{ "text":"$txt1[$to]", "pos":[300,600], "color":"#f0bf6e", "size":23, "align":"CENTER", - "wrap":"BREAK", - "style":"BOLD", - "strokeColor":null, - "strokeSize":1 + "style":"BOLD" },{ "text":"让$txt1[$to]告诉你吧", "pos":[10,50], "color":"#191919", "size":40, - "align":"LEFT", - "wrap":"NONE", - "style":"PLAIN", - "strokeColor":null, - "strokeSize":1 + "align":"LEFT" },{ "text":"啊这,ta说不知道", "pos":[10,750], "color":"#191919", "size":40, - "align":"LEFT", - "wrap":"NONE", - "style":"PLAIN", - "strokeColor":null, - "strokeSize":1 + "align":"LEFT" }], "alias":["问问"], "inRandomList":false diff --git a/data/xmmt.dituon.petpet/brain/data.json b/data/xmmt.dituon.petpet/brain/data.json index 946fc56b..475884b9 100644 --- a/data/xmmt.dituon.petpet/brain/data.json +++ b/data/xmmt.dituon.petpet/brain/data.json @@ -7,8 +7,6 @@ "pos": [[70,0,180,180],[70,0,180,180],[70,0,180,180],[70,0,180,180],[70,0,180,180],[70,0,180,180],[70,0,180,180],[70,0,180,180],[70,0,180,180],[70,0,180,180],[70,0,180,180],[70,0,180,180],[70,0,180,180],[70,0,180,180],[70,0,180,180],[70,0,180,180],[70,0,180,180],[70,0,180,180],[70,0,180,180],[70,0,180,180],[70,0,180,180],[70,0,180,180],[70,0,180,180],[70,0,180,180],[70,0,180,180],[70,0,180,180],[70,0,180,180],[70,0,180,180],[70,0,180,180],[70,0,180,180],[70,0,180,180],[70,0,180,180]], "style": [], "opacity": 0.5, - "rotate": false, - "round": false, "avatarOnTop": true, "fit": "COVER" } diff --git a/data/xmmt.dituon.petpet/cast/data.json b/data/xmmt.dituon.petpet/cast/data.json index c91c54a7..b0708a00 100644 --- a/data/xmmt.dituon.petpet/cast/data.json +++ b/data/xmmt.dituon.petpet/cast/data.json @@ -3,8 +3,6 @@ "avatar": [{ "type": "TO", "pos": [-16, 301, 158, 158], - "round": true, - "rotate": true, "angle": 288, "avatarOnTop": false }], diff --git a/data/xmmt.dituon.petpet/genshin_start/data.json b/data/xmmt.dituon.petpet/genshin_start/data.json index 175b4960..8d33ccbf 100644 --- a/data/xmmt.dituon.petpet/genshin_start/data.json +++ b/data/xmmt.dituon.petpet/genshin_start/data.json @@ -1 +1,14 @@ -{"type":"IMG","avatar":[{"type":"TO","posType":"DEFORM","pos":[[1,126],[42,377],[552,303],[545,1],[423,95]],"style":[],"opacity":1,"rotate":false,"round":false,"avatarOnTop":false}],"text":[],"alias":["启动"],"inRandomList":false} \ No newline at end of file +{ + "type": "IMG", + "avatar": [ + { + "type": "TO", + "posType": "DEFORM", + "pos": [[1, 126], [42, 377], [552, 303],[545, 1],[423, 95]], + "avatarOnTop": false + } + ], + "text": [], + "alias": ["启动"], + "inRandomList": false +} \ No newline at end of file diff --git a/data/xmmt.dituon.petpet/join/data.json b/data/xmmt.dituon.petpet/join/data.json index 3b6f590c..6361432b 100644 --- a/data/xmmt.dituon.petpet/join/data.json +++ b/data/xmmt.dituon.petpet/join/data.json @@ -1 +1,13 @@ -{"type":"IMG","avatar":[{"type":"TO","posType":"ZOOM","pos":[76,94,429,429],"style":[],"opacity":1,"rotate":false,"round":true,"avatarOnTop":false}],"text":[],"alias":["加入"],"inRandomList":false} \ No newline at end of file +{ + "type": "IMG", + "avatar": [ + { + "type": "TO", + "pos": [76, 94, 429, 429], + "avatarOnTop":false + } + ], + "text": [], + "alias": ["加入"], + "inRandomList": false +} \ No newline at end of file diff --git a/data/xmmt.dituon.petpet/kita/data.json b/data/xmmt.dituon.petpet/kita/data.json index 96bdbe74..cc29bbb0 100644 --- a/data/xmmt.dituon.petpet/kita/data.json +++ b/data/xmmt.dituon.petpet/kita/data.json @@ -1 +1,49 @@ -{"type": "GIF","avatar": [{"type": "TO","posType": "DEFORM","pos": [[[2,2],[1,146],[216,146],[214,1],[316,170]],[[2,2],[1,146],[216,146],[214,1],[315,169]],[[2,2],[1,146],[216,146],[214,1],[315,171]],[[3,2],[1,146],[218,145],[215,1],[314,171]],[[2,2],[1,146],[216,146],[214,1],[315,173]],[[4,1],[1,144],[216,145],[215,2],[315,174]],[[1,9],[7,155],[219,145],[213,1],[311,174]],[[1,34],[23,178],[236,146],[195,1],[290,174]],[[1,33],[23,178],[239,146],[199,1],[288,173]],[[1,33],[23,178],[239,146],[199,1],[288,172]],[[1,35],[21,182],[233,147],[204,1],[290,173]],[[1,33],[23,178],[239,146],[199,1],[289,173]],[[1,33],[23,178],[239,146],[199,1],[289,172]],[[1,33],[23,178],[239,146],[199,1],[289,172]],[[1,33],[23,178],[239,146],[199,1],[289,174]],[[1,33],[23,178],[239,146],[199,1],[289,172]],[[1,33],[23,178],[239,146],[199,1],[288,171]],[[1,33],[23,178],[239,146],[199,1],[288,173]],[[1,33],[23,178],[239,146],[199,1],[289,173]],[[1,34],[21,180],[240,149],[205,1],[291,173]],[[1,12],[8,158],[222,143],[213,1],[308,184]],[[1,3],[1,148],[213,144],[214,1],[317,188]],[[1,3],[1,148],[216,144],[214,1],[317,188]],[[1,3],[1,148],[216,144],[214,1],[317,188]],[[1,3],[1,148],[216,144],[214,1],[317,188]],[[1,3],[1,148],[216,144],[214,1],[317,189]],[[0,0],[0,0],[0,0],[0,0],[0,0]],[[0,0],[0,0],[0,0],[0,0],[0,0]],[[0,0],[0,0],[0,0],[0,0],[0,0]],[[0,0],[0,0],[0,0],[0,0],[0,0]]],"style": [],"rotate": false,"round": false,"avatarOnTop": false}],"text": [],"alias": ["喜多", "展示"],"inRandomList": true,"delay": 300} \ No newline at end of file +{ + "type": "GIF", + "avatar": [ + { + "type": "TO", + "posType": "DEFORM", + "pos":[ + [[2, 2], [1, 146], [216, 146], [214, 1], [316, 170]], + [[2, 2], [1, 146], [216, 146], [214, 1], [315, 169]], + [[2, 2], [1, 146], [216, 146], [214, 1], [315, 171]], + [[3, 2], [1, 146], [218, 145], [215, 1], [314, 171]], + [[2, 2], [1, 146], [216, 146], [214, 1], [315, 173]], + [[4, 1], [1, 144], [216, 145], [215, 2], [315, 174]], + [[1, 9], [7, 155], [219, 145], [213, 1], [311, 174]], + [[1, 34], [23, 178], [236, 146], [195, 1], [290, 174]], + [[1, 33], [23, 178], [239, 146], [199, 1], [288, 173]], + [[1, 33], [23, 178], [239, 146], [199, 1], [288, 172]], + [[1, 35], [21, 182], [233, 147], [204, 1], [290, 173]], + [[1, 33], [23, 178], [239, 146], [199, 1], [289, 173]], + [[1, 33], [23, 178], [239, 146], [199, 1], [289, 172]], + [[1, 33], [23, 178], [239, 146], [199, 1], [289, 172]], + [[1, 33], [23, 178], [239, 146], [199, 1], [289, 174]], + [[1, 33], [23, 178], [239, 146], [199, 1], [289, 172]], + [[1, 33], [23, 178], [239, 146], [199, 1], [288, 171]], + [[1, 33], [23, 178], [239, 146], [199, 1], [288, 173]], + [[1, 33], [23, 178], [239, 146], [199, 1], [289, 173]], + [[1, 34], [21, 180], [240, 149], [205, 1], [291, 173]], + [[1, 12], [8, 158], [222, 143], [213, 1], [308, 184]], + [[1, 3], [1, 148], [213, 144], [214, 1], [317, 188]], + [[1, 3], [1, 148], [216, 144], [214, 1], [317, 188]], + [[1, 3], [1, 148], [216, 144], [214, 1], [317, 188]], + [[1, 3], [1, 148], [216, 144], [214, 1], [317, 188]], + [[1, 3], [1, 148], [216, 144], [214, 1], [317, 189]], + [[0, 0], [0, 0], [0, 0], [0, 0], [0, 0]], + [[0, 0], [0, 0], [0, 0], [0, 0], [0, 0]], + [[0, 0], [0, 0], [0, 0], [0, 0], [0, 0]], + [[0, 0], [0, 0], [0, 0], [0, 0], [0, 0]] + ], + "avatarOnTop": false + } + ], + "text": [], + "alias": [ + "喜多", + "展示" + ], + "inRandomList": true, + "delay": 300 +} \ No newline at end of file diff --git a/data/xmmt.dituon.petpet/record/data.json b/data/xmmt.dituon.petpet/record/data.json index 738d6144..37257acd 100644 --- a/data/xmmt.dituon.petpet/record/data.json +++ b/data/xmmt.dituon.petpet/record/data.json @@ -4,8 +4,8 @@ "type": "TO", "pos": [[99,97,218,218],[99,97,218,218],[99,97,218,218],[99,97,218,218], [99,97,218,218],[99,97,218,218],[99,97,218,218],[99,97,218,218]], - "round": true, "rotate": true, + "origin": "CENTER", "avatarOnTop": false }], "text": [], diff --git a/data/xmmt.dituon.petpet/roll/data.json b/data/xmmt.dituon.petpet/roll/data.json index 484317ed..62cd2fc7 100644 --- a/data/xmmt.dituon.petpet/roll/data.json +++ b/data/xmmt.dituon.petpet/roll/data.json @@ -7,8 +7,8 @@ [87, 77, 220, 220], [96, 85, 220, 220], [92, 79, 220, 220], [92, 78, 220, 220], [92, 75, 220, 220], [92, 75, 220, 220], [93, 76, 220, 220], [90, 80, 220, 220] ], - "round": true, "rotate": true, + "origin": "CENTER", "avatarOnTop": false } ], diff --git a/data/xmmt.dituon.petpet/twist/data.json b/data/xmmt.dituon.petpet/twist/data.json index 32dbf5ab..a4fe11aa 100644 --- a/data/xmmt.dituon.petpet/twist/data.json +++ b/data/xmmt.dituon.petpet/twist/data.json @@ -7,8 +7,8 @@ [25, 66, 80, 80], [25, 66, 80, 80], [23, 68, 80, 80], [20, 69, 80, 80], [22, 68, 80, 80] ], - "round": true, "rotate": true, + "origin": "CENTER", "avatarOnTop": false } ], diff --git a/src/main/java/moe/dituon/petpet/share/AvatarModel.java b/src/main/java/moe/dituon/petpet/share/AvatarModel.java index 1d1fd2c2..862bbbe5 100644 --- a/src/main/java/moe/dituon/petpet/share/AvatarModel.java +++ b/src/main/java/moe/dituon/petpet/share/AvatarModel.java @@ -11,24 +11,19 @@ import java.util.ArrayList; import java.util.List; import java.util.Objects; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.stream.Collectors; public class AvatarModel { - private static final ExecutorService threadPool = Executors.newFixedThreadPool(BasePetService.DEFAULT_THREAD_POOL_SIZE); - - private Type imageType; protected AvatarType type; protected int[][] pos = {{0, 0, 100, 100}}; protected FitType fitType; protected short angle; + protected AvatarTransformOrigin transformOrigin; protected float opacity = 1.0F; protected boolean round; protected boolean rotate; protected boolean onTop; protected List imageList = null; + private Type imageType; private short posIndex = 0; private boolean antialias; private boolean resampling; @@ -64,6 +59,7 @@ private void buildData(AvatarData data, Type imageType) { opacity = data.getOpacity(); round = data.getRound(); rotate = data.getRotate(); + transformOrigin = data.getOrigin(); onTop = data.getAvatarOnTop(); antialias = Boolean.TRUE.equals(data.getAntialias()); resampling = Boolean.TRUE.equals(data.getResampling()); @@ -171,167 +167,125 @@ private int[] JsonArrayToIntArray(JsonArray ja) { } private void buildImage() { - if (cropType != CropType.NONE) imageList = ImageSynthesis.cropImage(imageList, cropType, cropPos); + if (imageList.size() == 1) { + imageList = List.of(buildImage(imageList.get(0))); + } else { + ImageSynthesis.execImageList(imageList, this::buildImage); + } + } + + public BufferedImage buildImage(BufferedImage image) { + if (cropType != CropType.NONE) { + image = ImageSynthesis.cropImage(image, cropType, cropPos); + } + + if (!styleList.isEmpty()) image = buildStyledImage(image); + if (!filterList.isEmpty()) image = buildFilteredImage(image); + + if (round) { + image = ImageSynthesis.convertCircular(image, antialias); + } + + if (resampling && posType == AvatarPosType.ZOOM) { + int aw = 0, ah = 0, maxSize = 0; + for (int[] p : pos) { + if (p[2] > aw) aw = p[2]; + if (p[3] > ah) ah = p[3]; + maxSize = Math.max(aw, ah); + } + + try { + image = Thumbnails.of(imageList.get(0)).size(maxSize, maxSize).keepAspectRatio(true).asBufferedImage(); + } catch (IOException ex) { + throw new RuntimeException(ex); + } + } + + return image; + } + + protected BufferedImage buildStyledImage(BufferedImage image) { for (AvatarStyle style : styleList) { switch (style) { case FLIP: - imageList = ImageSynthesis.flipImage(imageList); + image = ImageSynthesis.flipImage(image); break; case MIRROR: - imageList = ImageSynthesis.mirrorImage(imageList); + image = ImageSynthesis.mirrorImage(image); break; case GRAY: - imageList = ImageSynthesis.grayImage(imageList); + image = ImageSynthesis.grayImage(image); break; case BINARIZATION: - imageList = ImageSynthesis.binarizeImage(imageList); + image = ImageSynthesis.binarizeImage(image); break; } } + return image; + } + protected BufferedImage buildFilteredImage(BufferedImage image) { for (AvatarFilter filter : filterList) { if (filter instanceof AvatarSwirlFilter) { var swirlFilter = (AvatarSwirlFilter) filter; -// imageList.replaceAll(img -> { -// TwirlFilter twirlFilter = new TwirlFilter(); -// twirlFilter.setRadius(swirlFilter.getRadius()); -// twirlFilter.setAngle(swirlFilter.getAngle()); -// return twirlFilter.filter(img, null); -// }); - imageList = imageList.stream().map(img -> { - TwirlFilter tFilter = new TwirlFilter(); - tFilter.setRadius(swirlFilter.getRadius()); - tFilter.setAngle(swirlFilter.getAngle()); - return tFilter.filter(img, null); - }).collect(Collectors.toList()); + TwirlFilter tFilter = new TwirlFilter(); + tFilter.setRadius(swirlFilter.getRadius()); + tFilter.setAngle(swirlFilter.getAngle()); + image = tFilter.filter(image, null); } else if (filter instanceof AvatarBulgeFilter) { var bulgeFilter = (AvatarBulgeFilter) filter; - - imageList = imageList.stream().map(img -> { - SphereFilter sFilter = new SphereFilter(); - sFilter.setRadius(bulgeFilter.getRadius()); - sFilter.setRefractionIndex(Math.abs(bulgeFilter.getStrength()) + 1f); - return sFilter.filter(img, null); - }).collect(Collectors.toList()); + SphereFilter sFilter = new SphereFilter(); + sFilter.setRadius(bulgeFilter.getRadius()); + sFilter.setRefractionIndex(Math.abs(bulgeFilter.getStrength()) + 1f); + image = sFilter.filter(image, null); } else if (filter instanceof AvatarBlurFilter) { var blurFilter = (AvatarBlurFilter) filter; - - imageList = imageList.stream().map(img -> { - BoxBlurFilter bFilter = new BoxBlurFilter(); - bFilter.setRadius(blurFilter.getRadius()); - return bFilter.filter(img, null); - }).collect(Collectors.toList()); - } else if (filter instanceof AvatarContrastFilter){ + BoxBlurFilter bFilter = new BoxBlurFilter(); + bFilter.setRadius(blurFilter.getRadius()); + image = bFilter.filter(image, null); + } else if (filter instanceof AvatarContrastFilter) { var contrastFilter = (AvatarContrastFilter) filter; - - imageList = imageList.stream().map(img -> { - ContrastFilter cFilter = new ContrastFilter(); - cFilter.setContrast(contrastFilter.getContrast() + 1f); - cFilter.setBrightness(contrastFilter.getBrightness() + 1f); - return cFilter.filter(img, null); - }).collect(Collectors.toList()); + ContrastFilter cFilter = new ContrastFilter(); + cFilter.setContrast(contrastFilter.getContrast() + 1f); + cFilter.setBrightness(contrastFilter.getBrightness() + 1f); + image = cFilter.filter(image, null); } else if (filter instanceof AvatarHueFilter) { var hsbFilter = (AvatarHueFilter) filter; - - imageList = imageList.stream().map(img -> { - HSBAdjustFilter hFilter = new HSBAdjustFilter(); - hFilter.setHFactor(hsbFilter.getHue()); - hFilter.setSFactor(hsbFilter.getSaturation()); - hFilter.setBFactor(hsbFilter.getBrightness()); - return hFilter.filter(img, null); - }).collect(Collectors.toList()); + HSBAdjustFilter hFilter = new HSBAdjustFilter(); + hFilter.setHFactor(hsbFilter.getHue()); + hFilter.setSFactor(hsbFilter.getSaturation()); + hFilter.setBFactor(hsbFilter.getBrightness()); + image = hFilter.filter(image, null); } else if (filter instanceof AvatarHalftoneFilter) { var halftoneFilter = (AvatarHalftoneFilter) filter; - - imageList = imageList.stream().map(img -> { - ColorHalftoneFilter cFilter = new ColorHalftoneFilter(); - cFilter.setdotRadius(halftoneFilter.getRadius()); - float angle = halftoneFilter.getAngle(); - cFilter.setCyanScreenAngle(cFilter.getCyanScreenAngle() + angle); - cFilter.setMagentaScreenAngle(cFilter.getMagentaScreenAngle() + angle); - cFilter.setYellowScreenAngle(cFilter.getYellowScreenAngle() + angle); - return cFilter.filter(img, null); - }).collect(Collectors.toList()); + ColorHalftoneFilter cFilter = new ColorHalftoneFilter(); + cFilter.setdotRadius(halftoneFilter.getRadius()); + float angle = halftoneFilter.getAngle(); + cFilter.setCyanScreenAngle(cFilter.getCyanScreenAngle() + angle); + cFilter.setMagentaScreenAngle(cFilter.getMagentaScreenAngle() + angle); + cFilter.setYellowScreenAngle(cFilter.getYellowScreenAngle() + angle); + image = cFilter.filter(image, null); } else if (filter instanceof AvatarDotScreenFilter) { var dotScreenFilter = (AvatarDotScreenFilter) filter; - - imageList = imageList.stream().map(img -> { - ColorHalftoneFilter cFilter = new ColorHalftoneFilter(); - cFilter.setdotRadius(dotScreenFilter.getRadius()); - float angle = dotScreenFilter.getAngle(); - cFilter.setCyanScreenAngle(angle); - cFilter.setMagentaScreenAngle(angle); - cFilter.setYellowScreenAngle(angle); - return ImageSynthesis.grayImage(cFilter.filter(img, null)); - }).collect(Collectors.toList()); + ColorHalftoneFilter cFilter = new ColorHalftoneFilter(); + cFilter.setdotRadius(dotScreenFilter.getRadius()); + float angle = dotScreenFilter.getAngle(); + cFilter.setCyanScreenAngle(angle); + cFilter.setMagentaScreenAngle(angle); + cFilter.setYellowScreenAngle(angle); + image = ImageSynthesis.grayImage(cFilter.filter(image, null)); } else if (filter instanceof AvatarNoiseFilter) { var noiseFilter = (AvatarNoiseFilter) filter; - - imageList = imageList.stream().map(img -> { - NoiseFilter nFilter = new NoiseFilter(); - nFilter.setAmount(Math.round(noiseFilter.getAmount() * 100)); - return nFilter.filter(img, null); - }).collect(Collectors.toList()); + NoiseFilter nFilter = new NoiseFilter(); + nFilter.setAmount(Math.round(noiseFilter.getAmount() * 100)); + image = nFilter.filter(image, null); } else if (filter instanceof AvatarDenoiseFilter) { - imageList = imageList.stream().map(img -> { - MedianFilter dFilter = new MedianFilter(); - return dFilter.filter(img, null); - }).collect(Collectors.toList()); - } - } - - if (round) { - imageList = ImageSynthesis.convertCircular(imageList, antialias); - } - - if (resampling && posType == AvatarPosType.ZOOM) { -// int tw = 0, th = 0, ti = 0; -// for (int[] p : pos) { -// if (p[2] == 0 || p[3] == 0) continue; -// tw += p[2]; -// th += p[3]; -// ti++; -// } -// int aw = tw / ti, ah = th / ti; - int aw = 0, ah = 0, maxSize = 0; - for (int[] p : pos) { - if (p[2] > aw) aw = p[2]; - if (p[3] > ah) ah = p[3]; - maxSize = Math.max(aw, ah); - } - - imageList = new ArrayList<>(imageList); - if (imageList.size() == 1) { - try { - imageList.set(0, Thumbnails.of(imageList.get(0)).size(maxSize, maxSize).keepAspectRatio(true).asBufferedImage()); - } catch (IOException ex) { - throw new RuntimeException(ex); - } - return; - } - - final int faw = aw, fah = ah; - CountDownLatch latch = new CountDownLatch(imageList.size()); - for (short i = 0; i < imageList.size(); i++) { - short fi = i; - threadPool.execute(() -> { - try { - var img = Thumbnails.of(imageList.get(fi)).size(faw, fah).keepAspectRatio(false).asBufferedImage(); - imageList.set(fi, img); - } catch (IOException ex) { - throw new RuntimeException(ex); - } finally { - latch.countDown(); - } - }); - } - try { - latch.await(); - } catch (InterruptedException e) { - throw new RuntimeException(e); + MedianFilter dFilter = new MedianFilter(); + image = dFilter.filter(image, null); } } + return image; } public FitType getZoomType() { @@ -392,6 +346,10 @@ public float getAngle(short index) { return ((float) (360 / pos.length) * index) + angle; //GIF自动旋转 } + public AvatarTransformOrigin getTransformOrigin() { + return transformOrigin; + } + /** * 获取下一个坐标 * @@ -425,6 +383,36 @@ public DeformData getDeformData() { return deformData; } + public int getImageWidth() { + return this.getFirstImage().getWidth(); + } + + public int getImageHeight() { + return this.getFirstImage().getHeight(); + } + + public boolean isGif() { + return imageList.size() > 1; + } + + /** + * 获取头像下一帧, 超过索引长度会重新开始循环 (线程不安全) + * 应当使用index直接获取帧 + */ + public BufferedImage nextFrame() { + if (frameIndex >= imageList.size()) frameIndex = 0; + return imageList.get(frameIndex++); + } + + /** + * 获取指定帧数, 超过索引长度会从头计数 + * 例如: length: 8 index: 10 return: list[1] + */ + public BufferedImage getFrame(short i) { + i = (short) (i % imageList.size()); + return imageList.get(i); + } + public static class DeformData { static final int POS_SIZE = 4; Point2D[][] deformPos; @@ -481,34 +469,4 @@ public int[] getAnchor(short i) { return anchor[i]; } } - - public int getImageWidth() { - return this.getFirstImage().getWidth(); - } - - public int getImageHeight() { - return this.getFirstImage().getHeight(); - } - - public boolean isGif() { - return imageList.size() > 1; - } - - /** - * 获取头像下一帧, 超过索引长度会重新开始循环 (线程不安全) - * 应当使用index直接获取帧 - */ - public BufferedImage nextFrame() { - if (frameIndex >= imageList.size()) frameIndex = 0; - return imageList.get(frameIndex++); - } - - /** - * 获取指定帧数, 超过索引长度会从头计数 - * 例如: length: 8 index: 10 return: list[1] - */ - public BufferedImage getFrame(short i) { - i = (short) (i % imageList.size()); - return imageList.get(i); - } } \ No newline at end of file diff --git a/src/main/java/moe/dituon/petpet/share/BaseConfig.kt b/src/main/java/moe/dituon/petpet/share/BaseConfig.kt index f059c871..b1a1187c 100644 --- a/src/main/java/moe/dituon/petpet/share/BaseConfig.kt +++ b/src/main/java/moe/dituon/petpet/share/BaseConfig.kt @@ -142,6 +142,10 @@ enum class CropType { NONE, PIXEL, PERCENT } +enum class AvatarTransformOrigin { + DEFAULT, CENTER +} + enum class FitType { CONTAIN, COVER, FILL } @@ -227,6 +231,7 @@ data class AvatarData @JvmOverloads constructor( var style: List = emptyList(), var filter: List = emptyList(), var angle: Short = 0, + var origin: AvatarTransformOrigin = AvatarTransformOrigin.DEFAULT, var opacity: Float = 1.0F, var round: Boolean = false, var rotate: Boolean = false, diff --git a/src/main/java/moe/dituon/petpet/share/ImageSynthesis.java b/src/main/java/moe/dituon/petpet/share/ImageSynthesis.java index bf815516..ff73199a 100644 --- a/src/main/java/moe/dituon/petpet/share/ImageSynthesis.java +++ b/src/main/java/moe/dituon/petpet/share/ImageSynthesis.java @@ -5,8 +5,14 @@ import java.awt.image.BufferedImage; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.function.Function; public class ImageSynthesis extends ImageSynthesisCore { + protected static final ExecutorService threadPool = Executors.newFixedThreadPool(BasePetService.DEFAULT_THREAD_POOL_SIZE); + protected static void g2dDrawAvatar(Graphics2D g2d, AvatarModel avatar, short index) { g2dDrawAvatar(g2d, avatar, index, 1.0F); } @@ -17,8 +23,9 @@ protected static void g2dDrawAvatar(Graphics2D g2d, AvatarModel avatar, case ZOOM: g2dDrawZoomAvatar( g2d, avatar.getFrame(index), avatar.getPos(index), - avatar.getAngle(index), avatar.isRound(), multiple, - avatar.getZoomType(), avatar.getOpacity() + avatar.getAngle(index), + avatar.getTransformOrigin() == AvatarTransformOrigin.CENTER, + multiple, avatar.getZoomType(), avatar.getOpacity() ); break; case DEFORM: @@ -131,4 +138,28 @@ public static BufferedImage cropImage(BufferedImage image, CropType type, int[] public static List cropImage(List imageList, CropType type, int[] cropPos) { return cropImage(imageList, cropPos, type == CropType.PERCENT); } + + static List execImageList( + List imageList, + Function fun + ) { + try { + CountDownLatch latch = new CountDownLatch(imageList.size()); + List result = new ArrayList<>(imageList.size()); + + for (int i = 0; i < imageList.size(); i++) { + var fi = i; + threadPool.execute(() -> { + BufferedImage img = imageList.get(fi); + result.set(fi, fun.apply(img)); + latch.countDown(); + }); + } + + latch.await(); + return result; + } catch (InterruptedException ex){ + throw new RuntimeException(ex); + } + } } \ No newline at end of file diff --git a/src/main/java/moe/dituon/petpet/share/ImageSynthesisCore.java b/src/main/java/moe/dituon/petpet/share/ImageSynthesisCore.java index dc5c19a0..621b9a6c 100644 --- a/src/main/java/moe/dituon/petpet/share/ImageSynthesisCore.java +++ b/src/main/java/moe/dituon/petpet/share/ImageSynthesisCore.java @@ -22,32 +22,32 @@ public abstract class ImageSynthesisCore { /** * 在Graphics2D画布上 绘制缩放头像 * - * @param g2d Graphics2D 画布 - * @param avatarImage 处理后的头像 - * @param pos 处理后的坐标 (int[4]{x, y, w, h}) - * @param angle 旋转角, 对特殊角度有特殊处理分支 - * @param isRound 裁切为圆形 + * @param g2d Graphics2D 画布 + * @param avatarImage 处理后的头像 + * @param pos 处理后的坐标 (int[4]{x, y, w, h}) + * @param angle 旋转角, 对特殊角度有特殊处理分支 + * @param originAtCenter 旋转原点在中心 */ protected static void g2dDrawZoomAvatar(Graphics2D g2d, BufferedImage avatarImage, int[] pos, - float angle, boolean isRound) { - g2dDrawZoomAvatar(g2d, avatarImage, pos, angle, isRound, 1.0F, FitType.FILL, 1.0F); + float angle, boolean originAtCenter) { + g2dDrawZoomAvatar(g2d, avatarImage, pos, angle, originAtCenter, 1.0F, FitType.FILL, 1.0F); } /** * 在Graphics2D画布上 绘制缩放头像 * - * @param g2d Graphics2D 画布 - * @param avatarImage 处理后的头像 - * @param pos 处理后的坐标 (int[4]{x, y, w, h}) - * @param angle 旋转角, 对特殊角度有特殊处理分支 - * @param isRound 裁切为圆形 - * @param multiple 缩放倍数 - * @param fitType 显示策略 - * @param opacity 头像不透明度 + * @param g2d Graphics2D 画布 + * @param avatarImage 处理后的头像 + * @param pos 处理后的坐标 (int[4]{x, y, w, h}) + * @param angle 旋转角, 对特殊角度有特殊处理分支 + * @param originAtCenter 旋转原点在中心 + * @param multiple 缩放倍数 + * @param fitType 显示策略 + * @param opacity 头像不透明度 */ protected static void g2dDrawZoomAvatar( Graphics2D g2d, @NotNull BufferedImage avatarImage, int[] pos, - float angle, boolean isRound, float multiple, FitType fitType, float opacity + float angle, boolean originAtCenter, float multiple, FitType fitType, float opacity ) { int x = (int) (pos[0] * multiple); int y = (int) (pos[1] * multiple); @@ -104,7 +104,11 @@ protected static void g2dDrawZoomAvatar( } AffineTransform old = g2d.getTransform(); - g2d.rotate(Math.toRadians(angle), x, y); + if (originAtCenter) { + g2d.rotate(Math.toRadians(angle), (double) w / 2 + x, (double) h / 2 + y); + } else { + g2d.rotate(Math.toRadians(angle), x, y); + } g2d.drawImage(avatarImage, x, y, w, h, null); g2d.setTransform(old); }