Spaces:
Paused
Paused
| const { | |
| QuoteGenerate | |
| } = require('../utils') | |
| const { createCanvas, loadImage } = require('canvas') | |
| const sharp = require('sharp') | |
| const normalizeColor = (color) => { | |
| const canvas = createCanvas(0, 0) | |
| const canvasCtx = canvas.getContext('2d') | |
| canvasCtx.fillStyle = color | |
| color = canvasCtx.fillStyle | |
| return color | |
| } | |
| const colorLuminance = (hex, lum) => { | |
| hex = String(hex).replace(/[^0-9a-f]/gi, '') | |
| if (hex.length < 6) { | |
| hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2] | |
| } | |
| lum = lum || 0 | |
| // convert to decimal and change luminosity | |
| let rgb = '#' | |
| let c | |
| let i | |
| for (i = 0; i < 3; i++) { | |
| c = parseInt(hex.substr(i * 2, 2), 16) | |
| c = Math.round(Math.min(Math.max(0, c + (c * lum)), 255)).toString(16) | |
| rgb += ('00' + c).substr(c.length) | |
| } | |
| return rgb | |
| } | |
| const imageAlpha = (image, alpha) => { | |
| const canvas = createCanvas(image.width, image.height) | |
| const canvasCtx = canvas.getContext('2d') | |
| canvasCtx.globalAlpha = alpha | |
| canvasCtx.drawImage(image, 0, 0) | |
| return canvas | |
| } | |
| module.exports = async (parm) => { | |
| // console.log(JSON.stringify(parm, null, 2)) | |
| if (!parm) return { error: 'query_empty' } | |
| if (!parm.messages || parm.messages.length < 1) return { error: 'messages_empty' } | |
| let botToken = parm.botToken || process.env.BOT_TOKEN | |
| const quoteGenerate = new QuoteGenerate(botToken) | |
| const quoteImages = [] | |
| let backgroundColor = parm.backgroundColor || '//#292232' | |
| let backgroundColorOne | |
| let backgroundColorTwo | |
| const backgroundColorSplit = backgroundColor.split('/') | |
| if (backgroundColorSplit && backgroundColorSplit.length > 1 && backgroundColorSplit[0] !== '') { | |
| backgroundColorOne = normalizeColor(backgroundColorSplit[0]) | |
| backgroundColorTwo = normalizeColor(backgroundColorSplit[1]) | |
| } else if (backgroundColor.startsWith('//')) { | |
| backgroundColor = normalizeColor(backgroundColor.replace('//', '')) | |
| backgroundColorOne = colorLuminance(backgroundColor, 0.35) | |
| backgroundColorTwo = colorLuminance(backgroundColor, -0.15) | |
| } else { | |
| backgroundColor = normalizeColor(backgroundColor) | |
| backgroundColorOne = backgroundColor | |
| backgroundColorTwo = backgroundColor | |
| } | |
| for (const key in parm.messages) { | |
| const message = parm.messages[key] | |
| if (message) { | |
| const canvasQuote = await quoteGenerate.generate( | |
| backgroundColorOne, | |
| backgroundColorTwo, | |
| message, | |
| parm.width, | |
| parm.height, | |
| parseFloat(parm.scale), | |
| parm.emojiBrand | |
| ) | |
| quoteImages.push(canvasQuote) | |
| } | |
| } | |
| if (quoteImages.length === 0) { | |
| return { | |
| error: 'empty_messages' | |
| } | |
| } | |
| let canvasQuote | |
| if (quoteImages.length > 1) { | |
| let width = 0 | |
| let height = 0 | |
| for (let index = 0; index < quoteImages.length; index++) { | |
| if (quoteImages[index].width > width) width = quoteImages[index].width | |
| height += quoteImages[index].height | |
| } | |
| const quoteMargin = 5 * parm.scale | |
| const canvas = createCanvas(width, height + (quoteMargin * quoteImages.length)) | |
| const canvasCtx = canvas.getContext('2d') | |
| let imageY = 0 | |
| for (let index = 0; index < quoteImages.length; index++) { | |
| canvasCtx.drawImage(quoteImages[index], 0, imageY) | |
| imageY += quoteImages[index].height + quoteMargin | |
| } | |
| canvasQuote = canvas | |
| } else { | |
| canvasQuote = quoteImages[0] | |
| } | |
| let quoteImage | |
| let { type, format, ext } = parm | |
| if (!type && ext) type = 'png' | |
| if (type !== 'image' && type !== 'stories' && canvasQuote.height > 1024 * 2) type = 'png' | |
| if (type === 'quote') { | |
| const downPadding = 75 | |
| const maxWidth = 512 | |
| const maxHeight = 512 | |
| const imageQuoteSharp = sharp(canvasQuote.toBuffer()) | |
| if (canvasQuote.height > canvasQuote.width) imageQuoteSharp.resize({ height: maxHeight }) | |
| else imageQuoteSharp.resize({ width: maxWidth }) | |
| const canvasImage = await loadImage(await imageQuoteSharp.toBuffer()) | |
| const canvasPadding = createCanvas(canvasImage.width, canvasImage.height + downPadding) | |
| const canvasPaddingCtx = canvasPadding.getContext('2d') | |
| canvasPaddingCtx.drawImage(canvasImage, 0, 0) | |
| const imageSharp = sharp(canvasPadding.toBuffer()) | |
| if (canvasPadding.height >= canvasPadding.width) imageSharp.resize({ height: maxHeight }) | |
| else imageSharp.resize({ width: maxWidth }) | |
| if (format === 'png') quoteImage = await imageSharp.png().toBuffer() | |
| else quoteImage = await imageSharp.webp({ lossless: true, force: true }).toBuffer() | |
| } else if (type === 'image') { | |
| const heightPadding = 75 * parm.scale | |
| const widthPadding = 95 * parm.scale | |
| const canvasImage = await loadImage(canvasQuote.toBuffer()) | |
| const canvasPic = createCanvas(canvasImage.width + widthPadding, canvasImage.height + heightPadding) | |
| const canvasPicCtx = canvasPic.getContext('2d') | |
| // radial gradient background (top left) | |
| const gradient = canvasPicCtx.createRadialGradient( | |
| canvasPic.width / 2, | |
| canvasPic.height / 2, | |
| 0, | |
| canvasPic.width / 2, | |
| canvasPic.height / 2, | |
| canvasPic.width / 2 | |
| ) | |
| const patternColorOne = colorLuminance(backgroundColorTwo, 0.15) | |
| const patternColorTwo = colorLuminance(backgroundColorOne, 0.15) | |
| gradient.addColorStop(0, patternColorOne) | |
| gradient.addColorStop(1, patternColorTwo) | |
| canvasPicCtx.fillStyle = gradient | |
| canvasPicCtx.fillRect(0, 0, canvasPic.width, canvasPic.height) | |
| const canvasPatternImage = await loadImage('./assets/pattern_02.png') | |
| // const canvasPatternImage = await loadImage('./assets/pattern_ny.png'); | |
| const pattern = canvasPicCtx.createPattern(imageAlpha(canvasPatternImage, 0.3), 'repeat') | |
| canvasPicCtx.fillStyle = pattern | |
| canvasPicCtx.fillRect(0, 0, canvasPic.width, canvasPic.height) | |
| // Add shadow effect to the canvas image | |
| canvasPicCtx.shadowOffsetX = 8 | |
| canvasPicCtx.shadowOffsetY = 8 | |
| canvasPicCtx.shadowBlur = 13 | |
| canvasPicCtx.shadowColor = 'rgba(0, 0, 0, 0.5)' | |
| // Draw the image to the canvas with padding centered | |
| canvasPicCtx.drawImage(canvasImage, widthPadding / 2, heightPadding / 2) | |
| canvasPicCtx.shadowOffsetX = 0 | |
| canvasPicCtx.shadowOffsetY = 0 | |
| canvasPicCtx.shadowBlur = 0 | |
| canvasPicCtx.shadowColor = 'rgba(0, 0, 0, 0)' | |
| // write text button right | |
| canvasPicCtx.fillStyle = `rgba(0, 0, 0, 0.3)` | |
| canvasPicCtx.font = `${8 * parm.scale}px Noto Sans` | |
| canvasPicCtx.textAlign = 'right' | |
| canvasPicCtx.fillText('@QuotLyBot', canvasPic.width - 25, canvasPic.height - 25) | |
| quoteImage = await sharp(canvasPic.toBuffer()).png({ lossless: true, force: true }).toBuffer() | |
| } else if (type === 'stories') { | |
| const canvasPic = createCanvas(720, 1280) | |
| const canvasPicCtx = canvasPic.getContext('2d') | |
| // radial gradient background (top left) | |
| const gradient = canvasPicCtx.createRadialGradient( | |
| canvasPic.width / 2, | |
| canvasPic.height / 2, | |
| 0, | |
| canvasPic.width / 2, | |
| canvasPic.height / 2, | |
| canvasPic.width / 2 | |
| ) | |
| const patternColorOne = colorLuminance(backgroundColorTwo, 0.25) | |
| const patternColorTwo = colorLuminance(backgroundColorOne, 0.15) | |
| gradient.addColorStop(0, patternColorOne) | |
| gradient.addColorStop(1, patternColorTwo) | |
| canvasPicCtx.fillStyle = gradient | |
| canvasPicCtx.fillRect(0, 0, canvasPic.width, canvasPic.height) | |
| const canvasPatternImage = await loadImage('./assets/pattern_02.png') | |
| const pattern = canvasPicCtx.createPattern(imageAlpha(canvasPatternImage, 0.3), 'repeat') | |
| canvasPicCtx.fillStyle = pattern | |
| canvasPicCtx.fillRect(0, 0, canvasPic.width, canvasPic.height) | |
| // Add shadow effect to the canvas image | |
| canvasPicCtx.shadowOffsetX = 8 | |
| canvasPicCtx.shadowOffsetY = 8 | |
| canvasPicCtx.shadowBlur = 13 | |
| canvasPicCtx.shadowColor = 'rgba(0, 0, 0, 0.5)' | |
| let canvasImage = await loadImage(canvasQuote.toBuffer()) | |
| // мінімальний відступ від країв картинки | |
| const minPadding = 110 | |
| // resize canvasImage if it is larger than canvasPic + minPadding | |
| if (canvasImage.width > canvasPic.width - minPadding * 2 || canvasImage.height > canvasPic.height - minPadding * 2) { | |
| canvasImage = await sharp(canvasQuote.toBuffer()).resize({ | |
| width: canvasPic.width - minPadding * 2, | |
| height: canvasPic.height - minPadding * 2, | |
| fit: 'contain', | |
| background: { r: 0, g: 0, b: 0, alpha: 0 } | |
| }).toBuffer() | |
| canvasImage = await loadImage(canvasImage) | |
| } | |
| // розмістити canvasImage в центрі по горизонталі і вертикалі | |
| const imageX = (canvasPic.width - canvasImage.width) / 2 | |
| const imageY = (canvasPic.height - canvasImage.height) / 2 | |
| canvasPicCtx.drawImage(canvasImage, imageX, imageY) | |
| canvasPicCtx.shadowOffsetX = 0 | |
| canvasPicCtx.shadowOffsetY = 0 | |
| canvasPicCtx.shadowBlur = 0 | |
| // write text vertical left center text | |
| canvasPicCtx.fillStyle = `rgba(0, 0, 0, 0.4)` | |
| canvasPicCtx.font = `${16 * parm.scale}px Noto Sans` | |
| canvasPicCtx.textAlign = 'center' | |
| canvasPicCtx.translate(70, canvasPic.height / 2) | |
| canvasPicCtx.rotate(-Math.PI / 2) | |
| canvasPicCtx.fillText('@QuotLyBot', 0, 0) | |
| quoteImage = await sharp(canvasPic.toBuffer()).png({ lossless: true, force: true }).toBuffer() | |
| } else { | |
| quoteImage = canvasQuote.toBuffer() | |
| } | |
| const imageMetadata = await sharp(quoteImage).metadata() | |
| const width = imageMetadata.width | |
| const height = imageMetadata.height | |
| let image | |
| if (ext) image = quoteImage | |
| else image = quoteImage.toString('base64') | |
| return { | |
| image, | |
| type, | |
| width, | |
| height, | |
| ext | |
| } | |
| } | |