Profile: Composite profile using imagemagick

by spawning an external process. Very slow but it works.
This commit is contained in:
2022-09-11 23:50:43 +02:00
parent ef19697346
commit 2b682fc074
4 changed files with 73 additions and 11 deletions

View File

@@ -1,8 +1,10 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 600 300" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;"> <svg width="100%" height="100%" viewBox="0 0 1200 600" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<path id="BG" d="M600,6.097C600,2.732 597.268,0 593.903,0L6.097,0C2.732,0 0,2.732 0,6.097L0,293.903C0,297.268 2.732,300 6.097,300L593.903,300C597.268,300 600,297.268 600,293.903L600,6.097ZM13.189,42.808L163.189,42.808L163.189,292.808L13.189,292.808L13.189,42.808ZM267.866,150L352.866,150L352.866,291.667L267.866,291.667L267.866,150ZM175,150L260,150L260,291.667L175,291.667L175,150ZM360.366,150L445.366,150L445.366,291.667L360.366,291.667L360.366,150Z" style="fill:rgb(61,61,61);"/> <g id="BG" transform="matrix(2,0,0,2,-1.23161e-13,0)">
<g id="BADGES" transform="matrix(0.511675,0,0,0.501053,-25.2132,12.3596)"> <path d="M600,6.097C600,2.732 597.268,0 593.903,0L6.097,0C2.732,0 0,2.732 0,6.097L0,293.903C0,297.268 2.732,300 6.097,300L593.903,300C597.268,300 600,297.268 600,293.903L600,6.097ZM13.189,42.808L163.189,42.808L163.189,292.808L13.189,292.808L13.189,42.808ZM267.866,150L352.866,150L352.866,291.667L267.866,291.667L267.866,150ZM175,150L260,150L260,291.667L175,291.667L175,150ZM360.366,150L445.366,150L445.366,291.667L360.366,291.667L360.366,150Z" style="fill:rgb(61,61,61);"/>
</g>
<g id="BADGES" transform="matrix(1.02335,0,0,1.00211,-50.4264,24.7193)">
<path d="M1180.85,291.997C1180.85,282.452 1173.27,274.702 1163.92,274.702L953.493,274.702C944.146,274.702 936.558,282.452 936.558,291.997L936.558,540.146C936.558,549.691 944.146,557.44 953.493,557.44L1163.92,557.44C1173.27,557.44 1180.85,549.691 1180.85,540.146L1180.85,291.997Z" style="fill:rgb(235,235,235);"/> <path d="M1180.85,291.997C1180.85,282.452 1173.27,274.702 1163.92,274.702L953.493,274.702C944.146,274.702 936.558,282.452 936.558,291.997L936.558,540.146C936.558,549.691 944.146,557.44 953.493,557.44L1163.92,557.44C1173.27,557.44 1180.85,549.691 1180.85,540.146L1180.85,291.997Z" style="fill:rgb(235,235,235);"/>
<clipPath id="_clip1"> <clipPath id="_clip1">
<path d="M1180.85,291.997C1180.85,282.452 1173.27,274.702 1163.92,274.702L953.493,274.702C944.146,274.702 936.558,282.452 936.558,291.997L936.558,540.146C936.558,549.691 944.146,557.44 953.493,557.44L1163.92,557.44C1173.27,557.44 1180.85,549.691 1180.85,540.146L1180.85,291.997Z"/> <path d="M1180.85,291.997C1180.85,282.452 1173.27,274.702 1163.92,274.702L953.493,274.702C944.146,274.702 936.558,282.452 936.558,291.997L936.558,540.146C936.558,549.691 944.146,557.44 953.493,557.44L1163.92,557.44C1173.27,557.44 1180.85,549.691 1180.85,540.146L1180.85,291.997Z"/>
@@ -25,11 +27,11 @@
</g> </g>
</g> </g>
</g> </g>
<g id="PROFILE" transform="matrix(0.5,0,0,0.5,5.68434e-14,0)"> <g id="PROFILE">
<g id="BODY" transform="matrix(0.994765,0,0,1.386,-12.9709,-59.6438)"> <g id="BODY" transform="matrix(0.994765,0,0,1.386,-12.9709,-59.6438)">
<path d="M1177.13,71.893C1177.13,59.947 1163.62,50.248 1146.98,50.248L395.039,50.248C378.394,50.248 364.881,59.947 364.881,71.893L364.881,227.016C364.881,238.962 378.394,248.661 395.039,248.661L1146.98,248.661C1163.62,248.661 1177.13,238.962 1177.13,227.016L1177.13,71.893Z" style="fill:rgb(227,90,255);"/> <path d="M1177.13,61.071C1177.13,55.097 1170.38,50.248 1162.05,50.248L379.96,50.248C371.638,50.248 364.881,55.097 364.881,61.071L364.881,237.838C364.881,243.811 371.638,248.661 379.96,248.661L1162.05,248.661C1170.38,248.661 1177.13,243.811 1177.13,237.838L1177.13,61.071Z" style="fill:rgb(227,90,255);"/>
<clipPath id="_clip2"> <clipPath id="_clip2">
<path d="M1177.13,71.893C1177.13,59.947 1163.62,50.248 1146.98,50.248L395.039,50.248C378.394,50.248 364.881,59.947 364.881,71.893L364.881,227.016C364.881,238.962 378.394,248.661 395.039,248.661L1146.98,248.661C1163.62,248.661 1177.13,238.962 1177.13,227.016L1177.13,71.893Z"/> <path d="M1177.13,61.071C1177.13,55.097 1170.38,50.248 1162.05,50.248L379.96,50.248C371.638,50.248 364.881,55.097 364.881,61.071L364.881,237.838C364.881,243.811 371.638,248.661 379.96,248.661L1162.05,248.661C1170.38,248.661 1177.13,243.811 1177.13,237.838L1177.13,61.071Z"/>
</clipPath> </clipPath>
<g clip-path="url(#_clip2)"> <g clip-path="url(#_clip2)">
<g transform="matrix(1.25332,0,0,0.899538,-99.307,18.6818)"> <g transform="matrix(1.25332,0,0,0.899538,-99.307,18.6818)">
@@ -45,9 +47,9 @@
</g> </g>
</g> </g>
<g id="HEADER" transform="matrix(0.994765,0,0,0.3276,-12.9709,-6.46125)"> <g id="HEADER" transform="matrix(0.994765,0,0,0.3276,-12.9709,-6.46125)">
<path d="M1177.13,141.823C1177.13,91.281 1163.62,50.248 1146.98,50.248L395.039,50.248C378.394,50.248 364.881,91.281 364.881,141.823L364.881,248.661L1177.13,248.661L1177.13,141.823Z" style="fill:rgb(255,79,79);"/> <path d="M1177.13,96.036C1177.13,70.765 1170.38,50.248 1162.05,50.248L379.96,50.248C371.638,50.248 364.881,70.765 364.881,96.036L364.881,248.661L1177.13,248.661L1177.13,96.036Z" style="fill:rgb({{HEADER_COLOR}});"/>
<clipPath id="_clip3"> <clipPath id="_clip3">
<path d="M1177.13,141.823C1177.13,91.281 1163.62,50.248 1146.98,50.248L395.039,50.248C378.394,50.248 364.881,91.281 364.881,141.823L364.881,248.661L1177.13,248.661L1177.13,141.823Z"/> <path d="M1177.13,96.036C1177.13,70.765 1170.38,50.248 1162.05,50.248L379.96,50.248C371.638,50.248 364.881,70.765 364.881,96.036L364.881,248.661L1177.13,248.661L1177.13,96.036Z"/>
</clipPath> </clipPath>
<g clip-path="url(#_clip3)"> <g clip-path="url(#_clip3)">
<g transform="matrix(1.87138,0,0,5.68249,-1010.59,-70.3181)"> <g transform="matrix(1.87138,0,0,5.68249,-1010.59,-70.3181)">
@@ -65,7 +67,7 @@
<circle cx="399.913" cy="77.84" r="58.594" style="fill:rgb(79,255,171);"/> <circle cx="399.913" cy="77.84" r="58.594" style="fill:rgb(79,255,171);"/>
</g> </g>
</g> </g>
<g transform="matrix(1.42884,0,0,1.42884,-37.6688,-779.829)"> <g transform="matrix(2.85767,0,0,2.85767,-75.3376,-1559.66)">
<text x="44.839px" y="563.641px" style="font-family:'ArialMT', 'Arial', sans-serif;font-size:12px;">{{USERNAME}}</text> <text x="44.839px" y="563.641px" style="font-family:'ArialMT', 'Arial', sans-serif;font-size:12px;">{{USERNAME}}</text>
</g> </g>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 6.3 KiB

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

View File

@@ -1,6 +1,7 @@
const { SlashCommandBuilder, EmbedBuilder, ActionRowBuilder, ButtonBuilder, ButtonStyle } = require("discord.js"); const { SlashCommandBuilder, EmbedBuilder, ActionRowBuilder, ButtonBuilder, ButtonStyle } = require("discord.js");
const { Card, User, Character } = require("../models"); const { Card, User, Character } = require("../models");
const UserUtils = require("../util/users"); const { UserUtils, Compositing, Rendering } = require("../util");
const fs = require('fs');
const pageSize = 8; const pageSize = 8;
@@ -10,9 +11,26 @@ module.exports = {
.setName("profile") .setName("profile")
.setDescription("View your profile"), .setDescription("View your profile"),
async execute(interaction) { async execute(interaction) {
await interaction.deferReply();
let user = await UserUtils.getUserByDiscordId(interaction.member.id); let user = await UserUtils.getUserByDiscordId(interaction.member.id);
let profile = await user.getProfile(); let profile = await user.getProfile();
await interaction.reply(`json: ${JSON.stringify(profile)}`);
let profileTemplate = fs.readFileSync('/app/assets/profile/profile.svg').toString();
profileTemplate = profileTemplate.replace(/{{USERNAME}}/g, interaction.member.user.username);
profileTemplate = profileTemplate.replace(/{{HEADER_COLOR}}/g, '190,31,97');
let slots = ['slotOne', 'slotTwo', 'slotThree', 'slotFour'];
let renderedCards = [];
for (slot of slots) {
let card = await Card.findOne({ where: { id: profile[slot] }});
if (card) {
let cardImage = await Rendering.renderCard(card);
renderedCards.push(cardImage);
}
}
let profileImage = await Compositing.renderProfile(profile, profileTemplate, renderedCards);
await interaction.editReply({ files: [profileImage] });
} }
} }

42
util/compositing.js Normal file
View File

@@ -0,0 +1,42 @@
const { spawn } = require('child_process');
const crypto = require('crypto');
const fs = require('fs');
const { Card } = require('../models');
//TODO: Handle missing images
module.exports = {
name: "Compositing",
renderProfile: async function(profile, svgTemplate, renderedCards) {
let hash = crypto.createHash('md5').update(JSON.stringify(profile)).digest('hex');
let outFile = `/app/assets/image_cache/profiles/${hash}.gif`;
console.log('Rendering profile to ' + outFile);
//composite {overlay} {background} [{mask}] [-compose {method}] {result}
let args = ['svg:-', 'null:',
'\(', `${renderedCards[0]}`, '-coalesce', '\)',
'-geometry', '+25+85', '-compose', 'over', '-layers', 'composite',
'null:', '\(', `${renderedCards[1]}`, '-coalesce', '-resize', '170x283', '\)',
'-geometry', '+350+300', '-compose', 'over', '-layers', 'composite',
'null:', '\(', `${renderedCards[2]}`, '-coalesce', '-resize', '170x283', '\)',
'-geometry', '+535+300', '-compose', 'over', '-layers', 'composite',
'null:', '\(', `${renderedCards[3]}`, '-coalesce', '-resize', '170x283', '\)',
'-geometry', '+720+300', '-compose', 'over', '-layers', 'composite',
'-layers', 'optimize', outFile];
console.log('GM Args: ' + args);
const composite = spawn('convert', args);
composite.stdin.write(svgTemplate);
composite.stdin.end();
composite.stderr.on('data', (data) => {
console.log(`stdout: ${data}`);
});
const exitCode = await new Promise( (resolve, reject) => {
composite.on('close', resolve);
})
return outFile;
}
}