implement demotivator generation
This commit is contained in:
parent
b02c86a960
commit
ef8a2cb865
269
SimpleTGBot/MemeGen/DemotivatorGen.cs
Normal file
269
SimpleTGBot/MemeGen/DemotivatorGen.cs
Normal file
@ -0,0 +1,269 @@
|
||||
using System.Drawing;
|
||||
using System.Drawing.Imaging;
|
||||
using System.Drawing.Text;
|
||||
using System.Text;
|
||||
|
||||
namespace SimpleTGBot.MemeGen;
|
||||
|
||||
public class DemotivatorGen
|
||||
{
|
||||
private static FontFamily defaultFontFamily;
|
||||
|
||||
public DemotivatorGen()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
static DemotivatorGen()
|
||||
{
|
||||
try
|
||||
{
|
||||
defaultFontFamily = new FontFamily("DejaVu Serif");
|
||||
}
|
||||
catch
|
||||
{
|
||||
defaultFontFamily = new FontFamily(GenericFontFamilies.SansSerif);
|
||||
}
|
||||
}
|
||||
|
||||
public static DemotivatorStyle DefaultStyle()
|
||||
{
|
||||
return new DemotivatorStyle()
|
||||
{
|
||||
BorderThickness = 6,
|
||||
Padding = 10,
|
||||
OuterMargin = 20,
|
||||
CaptionSpacing = 32,
|
||||
Wtf1 = 60,
|
||||
OutlineColor = Color.FromArgb(255, 255, 255, 255),
|
||||
TitleColor = Color.FromArgb(255, 255, 255, 255),
|
||||
SubtitleColor = Color.FromArgb(255, 255, 255, 255),
|
||||
BackgroundColor = Color.FromArgb(255, 0, 0, 0),
|
||||
TitleFont = new Font(defaultFontFamily, 50),
|
||||
SubtitleFont = new Font(defaultFontFamily, 25),
|
||||
};
|
||||
}
|
||||
|
||||
#pragma warning disable CA1416 // мы точно работаем под Windows 7+ (проверено в Program.Main)
|
||||
public static MemoryStream MakePictureDemotivator(string picturePath, DemotivatorText[] texts, DemotivatorStyle style)
|
||||
{
|
||||
Bitmap picture = new Bitmap(picturePath);
|
||||
// данная bitmap предназначена только для подсчёта размера текста
|
||||
Bitmap bitmap = new Bitmap(1, 1);
|
||||
Graphics g = Graphics.FromImage(bitmap);
|
||||
Func<string, float> measureTitleString = s => g.MeasureString(s, style.TitleFont).Width;
|
||||
Func<string, float> measureSubtitleString = s => g.MeasureString(s, style.SubtitleFont).Width;
|
||||
|
||||
// код расчёта
|
||||
SizeF[] subdemSizes = new SizeF[texts.Length];
|
||||
float aspectRatio = (float)picture.Height / picture.Width;
|
||||
Console.WriteLine("aspect ratio " + aspectRatio);
|
||||
float scaledPictureWidth = Math.Clamp(picture.Width, 800, 1200);
|
||||
float scaledPictureHeight = scaledPictureWidth * aspectRatio;
|
||||
float contentWidth = scaledPictureWidth + style.Padding * 2;
|
||||
float contentHeight = scaledPictureHeight + style.Padding * 2;
|
||||
float titleFontHeight = style.TitleFont.GetHeight();
|
||||
float subtitleFontHeight = style.SubtitleFont.GetHeight();
|
||||
string[][] titles = new string[texts.Length][];
|
||||
string[][] subtitles = new string[texts.Length][];
|
||||
for (int i = 0; i < texts.Length; ++i)
|
||||
{
|
||||
Console.WriteLine("subdemotivator #" + i);
|
||||
float frameWidth = contentWidth + style.Padding * 2.0f + style.BorderThickness * 2.0f;
|
||||
Console.WriteLine("frame width " + frameWidth);
|
||||
float frameHeight = contentHeight + style.Padding * 2.0f + style.BorderThickness * 2.0f;
|
||||
|
||||
string title = texts[i].Title;
|
||||
WordWrapResult titleWrap = wordWrap(title, frameWidth + style.Wtf1 * 2f, frameWidth * 1.5f, measureTitleString);
|
||||
// captions.set(i * 2, titleWrap.wrappedString)
|
||||
titles[i] = titleWrap.lines;
|
||||
Console.WriteLine("title width " + titleWrap.actualWidth);
|
||||
|
||||
string subtitle = texts[i].Subtitle;
|
||||
WordWrapResult subtitleWrap = wordWrap(subtitle, frameWidth + style.Wtf1 * 2f, titleWrap.actualWidth, measureSubtitleString);
|
||||
// captions.set(i * 2 + 1, subtitleWrap.wrappedString)
|
||||
subtitles[i] = subtitleWrap.lines;
|
||||
Console.WriteLine("subtitle width " + subtitleWrap.actualWidth);
|
||||
|
||||
float subdemWidth = Math.Max(frameWidth, Math.Max(titleWrap.actualWidth, subtitleWrap.actualWidth));
|
||||
subdemWidth += style.Padding * 2f;
|
||||
Console.WriteLine("subdemotivator width: " + subdemWidth);
|
||||
|
||||
// Console.WriteLine("\"" + titleWrap.wrappedString + "\"");
|
||||
// Console.WriteLine("\"" + subtitleWrap.wrappedString + "\"");
|
||||
int titleLineCount = titleWrap.lines.Length;
|
||||
int subtitleLineCount = subtitleWrap.lines.Length;
|
||||
Console.WriteLine(titleLineCount + " lines of title, " + subtitleLineCount + " lines of subtitle");
|
||||
float titleHeight = titleFontHeight * titleLineCount;
|
||||
float subtitleHeight = subtitleFontHeight * subtitleLineCount;
|
||||
|
||||
float subdemHeight = style.Padding * 2f + frameHeight + titleHeight + subtitleHeight;
|
||||
subdemSizes[i] = new SizeF(subdemWidth, subdemHeight);
|
||||
Console.WriteLine("subdemotivator size: " + subdemWidth + "x" + subdemHeight);
|
||||
contentWidth = subdemWidth;
|
||||
contentHeight = subdemHeight;
|
||||
Console.WriteLine("-------------------------");
|
||||
}
|
||||
contentHeight += style.OuterMargin * 2f;
|
||||
|
||||
g.Dispose();
|
||||
bitmap = new Bitmap((int)contentWidth + (int)style.OuterMargin * 2, (int)contentHeight + (int)style.OuterMargin * 2);
|
||||
g = Graphics.FromImage(bitmap);
|
||||
|
||||
// код рисования
|
||||
SolidBrush backgroundBrush = new SolidBrush(style.BackgroundColor);
|
||||
SolidBrush outlineBrush = new SolidBrush(style.OutlineColor);
|
||||
SolidBrush titleBrush = new SolidBrush(style.TitleColor);
|
||||
SolidBrush subtitleBrush = new SolidBrush(style.SubtitleColor);
|
||||
g.FillRectangle(backgroundBrush, 0, 0, bitmap.Width, bitmap.Height);
|
||||
|
||||
PointF currentOrigin = new PointF(style.OuterMargin, style.OuterMargin);
|
||||
for (int j = texts.Length - 1; j >= 0; --j)
|
||||
{
|
||||
float contWidth = j != 0 ? subdemSizes[j - 1].Width : (scaledPictureWidth + style.Padding * 2f);
|
||||
float contHeight = j != 0 ? subdemSizes[j - 1].Height : (scaledPictureHeight + style.Padding * 2f);
|
||||
|
||||
float availableWidth = subdemSizes[j].Width;
|
||||
float contX = currentOrigin.X + (availableWidth - contWidth) / 2f;
|
||||
float contY = currentOrigin.Y + style.Padding * 2f;
|
||||
|
||||
float bt = style.BorderThickness;
|
||||
float pad = style.Padding;
|
||||
float capSp = style.CaptionSpacing;
|
||||
|
||||
g.FillRectangle(outlineBrush, contX, contY, contWidth, contHeight);
|
||||
g.FillRectangle(backgroundBrush, contX + bt, contY + bt, contWidth - bt * 2f, contHeight - bt * 2f);
|
||||
if (j == 0)
|
||||
{
|
||||
Console.WriteLine($"drawing image at ({contX+bt+pad}; {contY+bt+pad}) with size {contWidth-bt*2-pad*2}x{contHeight-bt*2-pad*2}");
|
||||
g.DrawImage(picture, contX + bt + pad, contY + bt + pad, contWidth - bt * 2 - pad * 2, contHeight - bt * 2 - pad * 2);
|
||||
}
|
||||
|
||||
float titleY = contY + contHeight + capSp;// + style.TitleFont.GetHeight();
|
||||
foreach (string titleLine in titles[j])
|
||||
{
|
||||
float titleX = currentOrigin.X + (availableWidth - g.MeasureString(titleLine, style.TitleFont).Width) / 2f;
|
||||
g.DrawString(titleLine, style.TitleFont, titleBrush, titleX, titleY);
|
||||
titleY += titleFontHeight;
|
||||
}
|
||||
|
||||
float subtitleY = titleY/* - titleFontHeight*/ + capSp;
|
||||
string[] subtitleLines = subtitles[j];
|
||||
if (subtitleLines.Length > 0)
|
||||
{
|
||||
// subtitleY += subtitleFontHeight;
|
||||
foreach (string subtitleLine in subtitleLines)
|
||||
{
|
||||
float subtitleX = currentOrigin.X + (availableWidth - measureSubtitleString(subtitleLine)) / 2f;
|
||||
g.DrawString(subtitleLine, style.SubtitleFont, subtitleBrush, subtitleX, subtitleY);
|
||||
subtitleY += subtitleFontHeight;
|
||||
}
|
||||
}
|
||||
Console.WriteLine("sub-demotivator " + j + " height is " + (subtitleY - currentOrigin.Y));
|
||||
currentOrigin.X += bt + (availableWidth - contWidth) / 2f;
|
||||
currentOrigin.Y += bt + pad;
|
||||
}
|
||||
|
||||
MemoryStream outStream = new MemoryStream();
|
||||
bitmap.Save(outStream, ImageFormat.Png);
|
||||
outStream.Seek(0, SeekOrigin.Begin);
|
||||
return outStream;
|
||||
}
|
||||
|
||||
private static WordWrapResult wordWrap(string rawText, float width, float maxWidth, Func<string, float> measureString)
|
||||
{
|
||||
float free = width;
|
||||
StringBuilder wrappedText = new StringBuilder();
|
||||
int words = (rawText.Length != 0) ? (rawText.Count(c => c == ' ') + 1) : 0;
|
||||
int rawPosition = 0;
|
||||
float actualWidth = 0;
|
||||
bool trailingReturn = false;
|
||||
while (rawPosition < rawText.Length)
|
||||
{
|
||||
string word = takeWord(rawText, rawPosition);
|
||||
float wordWidth = measureString(word + ' ');
|
||||
if (wordWidth <= free)
|
||||
{
|
||||
wrappedText.Append(word);
|
||||
wrappedText.Append(' ');
|
||||
trailingReturn = false;
|
||||
free -= wordWidth;
|
||||
actualWidth = Math.Max(width - free, actualWidth);
|
||||
rawPosition += word.Length + 1;
|
||||
words--;
|
||||
continue; // TODO заменить if на else-if чтобы убрать continue
|
||||
}
|
||||
if (wordWidth <= width)
|
||||
{
|
||||
if (!trailingReturn)
|
||||
wrappedText.Append("\n");
|
||||
wrappedText.Append(word);
|
||||
wrappedText.Append(" ");
|
||||
trailingReturn = false;
|
||||
free = width - wordWidth;
|
||||
rawPosition += word.Length + 1;
|
||||
words--;
|
||||
continue;
|
||||
}
|
||||
if (wordWidth <= maxWidth)
|
||||
{
|
||||
actualWidth = Math.Max(actualWidth, wordWidth);
|
||||
if (!trailingReturn)
|
||||
wrappedText.Append("\n");
|
||||
wrappedText.Append(word);
|
||||
wrappedText.Append("\n");
|
||||
trailingReturn = true;
|
||||
free = width;
|
||||
rawPosition += word.Length + 1;
|
||||
words--;
|
||||
continue;
|
||||
}
|
||||
|
||||
// TODO заменить на что-то поэффективнее
|
||||
float substrWidth = 0;
|
||||
int substrLength = word.Length;
|
||||
for (int c = 0; c < word.Length; ++c)
|
||||
{
|
||||
float charWidth = measureString(word[c].ToString());
|
||||
if (substrWidth + charWidth > maxWidth)
|
||||
{
|
||||
substrLength = c;
|
||||
break;
|
||||
}
|
||||
substrWidth += charWidth;
|
||||
}
|
||||
if (!trailingReturn)
|
||||
wrappedText.Append("\n");
|
||||
wrappedText.Append(word.Substring(0, substrLength));
|
||||
wrappedText.Append("\n");
|
||||
trailingReturn = true;
|
||||
free = width;
|
||||
rawPosition += substrLength;
|
||||
actualWidth = maxWidth;
|
||||
}
|
||||
return new WordWrapResult()
|
||||
{
|
||||
lines = wrappedText.ToString().Split('\n'),
|
||||
actualWidth = actualWidth,
|
||||
};
|
||||
}
|
||||
|
||||
private static string takeWord(string text, int position)
|
||||
{
|
||||
for (int i = position; i < text.Length; i++)
|
||||
{
|
||||
char c = text[i];
|
||||
if (c == ' ')
|
||||
{
|
||||
return text.Substring(position, i - position);
|
||||
}
|
||||
}
|
||||
return text.Substring(position);
|
||||
}
|
||||
|
||||
struct WordWrapResult
|
||||
{
|
||||
public string[] lines;
|
||||
public float actualWidth;
|
||||
}
|
||||
}
|
23
SimpleTGBot/MemeGen/Types.cs
Normal file
23
SimpleTGBot/MemeGen/Types.cs
Normal file
@ -0,0 +1,23 @@
|
||||
using System.Drawing;
|
||||
|
||||
namespace SimpleTGBot.MemeGen;
|
||||
|
||||
public record DemotivatorText {
|
||||
public string Title { get; init; }
|
||||
public string Subtitle { get; init; }
|
||||
}
|
||||
|
||||
public record DemotivatorStyle
|
||||
{
|
||||
public float BorderThickness { get; set; }
|
||||
public float Padding { get; set; }
|
||||
public float OuterMargin { get; set; }
|
||||
public float CaptionSpacing { get; set; }
|
||||
public float Wtf1 { get; set; }
|
||||
public Color OutlineColor { get; set; }
|
||||
public Color TitleColor { get; set; }
|
||||
public Color SubtitleColor { get; set; }
|
||||
public Color BackgroundColor { get; set; }
|
||||
public Font TitleFont { get; set; }
|
||||
public Font SubtitleFont { get; set; }
|
||||
}
|
@ -5,8 +5,13 @@ namespace SimpleTGBot;
|
||||
public static class Program
|
||||
{
|
||||
// Метод main немного видоизменился для асинхронной работы
|
||||
public static async Task Main(string[] args)
|
||||
public static async Task<int> Main(string[] args)
|
||||
{
|
||||
if (Environment.OSVersion.Platform != PlatformID.Win32NT || Environment.OSVersion.Version < new Version(6, 1))
|
||||
{
|
||||
Console.WriteLine("К сожалению, из-за используемых графических функций бот поддерживает только Windows начиная с версии 7.");
|
||||
return 8;
|
||||
}
|
||||
// Православная кодировка
|
||||
Console.OutputEncoding = Encoding.UTF8;
|
||||
|
||||
@ -26,5 +31,7 @@ public static class Program
|
||||
|
||||
TelegramBot telegramBot = new TelegramBot(botToken);
|
||||
await telegramBot.Run();
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
@ -8,6 +8,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="System.Drawing.Common" Version="9.0.4" />
|
||||
<PackageReference Include="Telegram.Bot" Version="19.0.0-preview.2" />
|
||||
</ItemGroup>
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
using Telegram.Bot;
|
||||
using SimpleTGBot.MemeGen;
|
||||
using Telegram.Bot;
|
||||
using Telegram.Bot.Exceptions;
|
||||
using Telegram.Bot.Polling;
|
||||
using Telegram.Bot.Types;
|
||||
@ -76,6 +77,10 @@ botQuit:
|
||||
chatId: chatId,
|
||||
text: "Ты написал:\n" + messageText,
|
||||
cancellationToken: cancellationToken);
|
||||
|
||||
// грязный тест
|
||||
MemoryStream demotivatorPng = DemotivatorGen.MakePictureDemotivator("pic.png", [new DemotivatorText() {Title=messageText, Subtitle=messageText}], DemotivatorGen.DefaultStyle());
|
||||
await botClient.SendPhotoAsync(message.Chat.Id, new InputFile(demotivatorPng, "dem.png"));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
Loading…
Reference in New Issue
Block a user