add stroke text, stretch symbols

This commit is contained in:
GenevensiS
2026-01-13 04:56:02 +01:00
parent 775bdac085
commit 6ca8abae1a
28 changed files with 766 additions and 245 deletions
+12
View File
@@ -122,6 +122,8 @@ menu:
help: 帮助
index: 索引... F1
website: 官方网站...
#TODO: Localize
documentation: &Documentation...
about: 关于Magic Set Editor...
## symbol editor menus
@@ -258,6 +260,8 @@ help:
# help menu
index: 指数
website: 网站
#TODO: Localize
documentation: Open the MSE documentation website
about: 关于
# new set window
@@ -726,6 +730,9 @@ label:
installer status: 地位
no version: -
#TODO: Localize
load image: Double click to load image
# print dialog
#TODO: Localize section
put space between cards: Put space between cards?
@@ -801,6 +808,11 @@ button:
if internet connection exists: 如果接入互联网
never: 从不
internal image extension: 内部存储带有文件扩展名的图像
#TODO: Localize
allow image download:
Allow downloading of images.
This may significantly increase the
duration of JSON and CSV imports.
# column select
move up: 向上移动
+12
View File
@@ -122,6 +122,8 @@ menu:
help: 幫助
index: 索引... F1
website: 官方網站...
#TODO: Localize
documentation: &Documentation...
about: 關於Magic Set Editor...
## symbol editor menus
@@ -258,6 +260,8 @@ help:
# help menu
index: 指數
website: 網站
#TODO: Localize
documentation: Open the MSE documentation website
about: 關於
# new set window
@@ -724,6 +728,9 @@ label:
installer status: 地位
no version: -
#TODO: Localize
load image: Double click to load image
# print dialog
#TODO: Localize section
put space between cards: Put space between cards?
@@ -799,6 +806,11 @@ button:
if internet connection exists: 如果接入互聯網
never: 從不
internal image extension: 內部儲存帶有檔案副檔名的映像
#TODO: Localize
allow image download:
Allow downloading of images.
This may significantly increase the
duration of JSON and CSV imports.
# column select
move up: 向上移動
+12
View File
@@ -127,6 +127,8 @@ menu:
help: &Hjælp
index: &Indeks... F1
website: &Hjemmeside...
#TODO: Localize
documentation: &Documentation...
about: &Om Magic Set Editor...
## symbol editor menus
@@ -264,6 +266,8 @@ help:
# help menu
index: Indeks
website: Internet side
#TODO: Localize
documentation: Open the MSE documentation website
about: Om
# new set window
@@ -742,6 +746,9 @@ label:
installer status: Status:
no version: -
#TODO: Localize
load image: Double click to load image
# print dialog
#TODO: Localize section
put space between cards: Put space between cards?
@@ -817,6 +824,11 @@ button:
if internet connection exists: Hvis internetforbindelse findes
never: Aldrig
internal image extension: Gem billeder internt med filtypenavn
#TODO: Localize
allow image download:
Allow downloading of images.
This may significantly increase the
duration of JSON and CSV imports.
# column select
move up: Flyt &Op
+14
View File
@@ -116,6 +116,7 @@ menu:
help: &Hilfe
index: &Index... F1
website: &Website...
documentation: &Dokumentation...
about: &Über Magic Set Editor...
## symbol editor menus
@@ -246,6 +247,7 @@ help:
# help menu
index: Index öffnen
website: MSE-Website öffnen
documentation: MSE-Dokumentationswebsite öffnen
about: Informationen zu dieser App anzeigen
# new set window
@@ -693,6 +695,8 @@ label:
installer status: Status:
no version: -
load image: Doppelklicken Sie, um ein Bild zu laden
# print dialog
put space between cards: Soll zwischen den Karten ein Leerzeichen eingefügt werden?
spacing print: Kartenabstand in Millimetern
@@ -761,6 +765,10 @@ button:
if internet connection exists: Wenn Internetverbindung besteht
never: Niemals
internal image extension: Speichern Sie Bilder intern mit der Dateierweiterung
allow image download:
Bilder herunterladen zulassen.
Dies kann die Dauer von JSON- und
CSV-Importen erheblich verlängern.
# column select
move up: A&ufwärts
@@ -1050,6 +1058,12 @@ error:
bulk modify nothing: Es wurden keine Karten geändert.
bulk modify success: Anzahl der erfolgreich geänderten Karten: %s
# web request
web request failed: Webanfrage fehlgeschlagen
web request cant create: Webanfrageobjekt konnte nicht erstellt werden
web request corrupted: Download erfolgreich, aber Daten sind beschädigt
web request unsupported format: Download erfolgreich, aber Daten haben ein nicht unterstütztes Format
# stats panel
dimension not found: Keine Statistikdimension '%s'
+2
View File
@@ -695,6 +695,8 @@ label:
installer status: Status:
no version: -
load image: Double click to load image
# print dialog
put space between cards: Put space between cards?
spacing print: Spacing between cards in millimeters
+12
View File
@@ -122,6 +122,8 @@ menu:
help: &Ayuda
index: &Índice... F1
website: &Página web...
#TODO: Localize
documentation: &Documentation...
about: &Sobre Magic Set Editor...
## symbol editor menus
@@ -258,6 +260,8 @@ help:
# help menu
index: Índice
website: Sitio web
#TODO: Localize
documentation: Open the MSE documentation website
about: Acerca
# new set window
@@ -727,6 +731,9 @@ label:
installer status: Estado:
no version: -
#TODO: Localize
load image: Double click to load image
# print dialog
#TODO: Localize section
put space between cards: Put space between cards?
@@ -802,6 +809,11 @@ button:
if internet connection exists: Si hay conexión de internet
never: Nunca
internal image extension: Almacenar imágenes internamente con extensión de archivo
#TODO: Localize
allow image download:
Allow downloading of images.
This may significantly increase the
duration of JSON and CSV imports.
# column select
move up: Mover &hacia arriba
+14
View File
@@ -116,6 +116,7 @@ menu:
help: &Aide
index: &Index... F1
website: &Site Web...
documentation: &Documentation...
about: &À propos de Magic Set Editor...
## symbol editor menus
@@ -246,6 +247,7 @@ help:
# help menu
index: Ouvrir l'index
website: Ouvrir le site web de Magic Set Editor
documentation: Ouvrir le site web de documentation de Magic Set Editor
about: Informations concernant Magic Set Editor
# new set window
@@ -694,6 +696,8 @@ label:
installer status: État:
no version: -
load image: Double-cliquer pour charger une image
# print dialog
put space between cards: Ajouter un espace entre les cartes?
spacing print: Espace entre les cartes en millimètres
@@ -762,6 +766,10 @@ button:
if internet connection exists: Si il y a une connexion internet
never: Jamais
internal image extension: Stocker les images avec une extension en interne
allow image download:
Autoriser le téléchargement des images.
Cela peut considérablement augmenter la
durée des importations de JSON et CSV.
# column select
move up: Rem&onter
@@ -1051,6 +1059,12 @@ error:
bulk modify nothing: Aucune carte n'a été modifiée.
bulk modify success: Nombre de cartes modifiées avec succès : %s
# web request
web request failed: Échec de la requête Web
web request cant create: Impossible de créer l'objet de requête Web
web request corrupted: Téléchargement réussi, mais les données sont corrompues
web request unsupported format: Téléchargement réussi, mais le format des données n'est pas pris en charge
# stats panel
dimension not found: Il n'y a pas de statistique de type '%s'
+14
View File
@@ -116,6 +116,7 @@ menu:
help: &Aiuto
index: &Indice... F1
website: &Sito web...
documentation: &Documentazione...
about: Informazioni su Magic Set Editor...
## symbol editor menus
@@ -246,6 +247,7 @@ help:
# help menu
index: Apri l'indice
website: Apri il sito web MSE
documentation: Apri il sito web della documentazione MSE
about: Mostra informazioni su questa app
# new set window
@@ -694,6 +696,8 @@ label:
installer status: Stato:
no version: -
load image: Fai doppio clic per caricare un'immagine
# print dialog
put space between cards: Inserire uno spazio tra le carte?
spacing print: Spaziatura tra le carte in millimetri
@@ -762,6 +766,10 @@ button:
if internet connection exists: Se è presente una connessione a Internet
never: Mai
internal image extension: Memorizza le immagini internamente con l'estensione del file
allow image download:
Consenti il download delle immagini.
Questo potrebbe aumentare significativamente
la durata delle importazioni JSON e CSV.
# column select
move up: Muovi S&u
@@ -1051,6 +1059,12 @@ error:
bulk modify nothing: Nessuna scheda è stata modificata.
bulk modify success: Numero di schede modificate correttamente: %s.
# web request
web request failed: Richiesta Web non riuscita
web request cant create: Impossibile creare l'oggetto della richiesta Web
web request corrupted: Download riuscito, ma i dati sono danneggiati
web request unsupported format: Download riuscito, ma i dati sono in un formato non supportato
# stats panel
dimension not found: Non c'è dimensione di statistiche '%s'
+12
View File
@@ -122,6 +122,8 @@ menu:
help: &ヘルプ
index: &索引... F1
website: &Magic Set Editorのウェブサイトを開く...
#TODO: Localize
documentation: &Documentation...
about: &Magic Set Editorについて...
## symbol editor menus
@@ -258,6 +260,8 @@ help:
# help menu
index: 索引
website: Webサイト
#TODO: Localize
documentation: Open the MSE documentation website
about: について
# new set window
@@ -726,6 +730,9 @@ label:
installer status: 状態
no version: -
#TODO: Localize
load image: Double click to load image
# print dialog
#TODO: Localize section
put space between cards: Put space between cards?
@@ -801,6 +808,11 @@ button:
if internet connection exists: インターネットに接続していれば
never: 行わない
internal image extension: ファイル拡張子を付けて画像を内部に保存する
#TODO: Localize
allow image download:
Allow downloading of images.
This may significantly increase the
duration of JSON and CSV imports.
# column select
move up: 上へ移動
+12
View File
@@ -122,6 +122,8 @@ menu:
help: &도움말
index: &색인... F1
website: &Magic Set Editor 웹사이트...
#TODO: Localize
documentation: &Documentation...
about: &Magic Set Editor 소개...
## symbol editor menus
@@ -259,6 +261,8 @@ help:
#TODO: Localize Section
index: Open the index
website: Open the MSE website
#TODO: Localize
documentation: Open the MSE documentation website
about: Show information about this app
# new set window
@@ -732,6 +736,9 @@ label:
installer status: 상태:
no version: -
#TODO: Localize
load image: Double click to load image
# print dialog
#TODO: Localize section
put space between cards: Put space between cards?
@@ -807,6 +814,11 @@ button:
if internet connection exists: 인터넷 연결이 존재하는 경우
never: 절대
internal image extension: 파일 확장자로 이미지 저장
#TODO: Localize
allow image download:
Allow downloading of images.
This may significantly increase the
duration of JSON and CSV imports.
# column select
move up: 숭진시키다
+12
View File
@@ -137,6 +137,8 @@ menu:
help: &Pomoc
index: Spis &treści... F1
website: &Strona internetowa...
#TODO: Localize
documentation: &Documentation...
about: &O programie Magic Set Editor...
## symbol editor menus
@@ -282,6 +284,8 @@ help:
#TODO: Localize Section
index: Open the index
website: Open the MSE website
#TODO: Localize
documentation: Open the MSE documentation website
about: Show information about this app
# new set window
@@ -776,6 +780,9 @@ label:
installer status: Stan:
no version: -
#TODO: Localize
load image: Double click to load image
# print dialog
#TODO: Localize section
put space between cards: Put space between cards?
@@ -855,6 +862,11 @@ button:
never: Nigdy
#TODO: Localize
internal image extension: Store images internally with file extension
#TODO: Localize
allow image download:
Allow downloading of images.
This may significantly increase the
duration of JSON and CSV imports.
# column select
move up: Przesuń w &górę
+14
View File
@@ -116,6 +116,7 @@ menu:
help: &Ajuda
index: &Indíce... F1
website: &Site...
documentation: &Documentação...
about: &About Magic Set Editor...
## symbol editor menus
@@ -246,6 +247,7 @@ help:
# help menu
index: Abrir o índice
website: Abrir o site do MSE
documentation: Abrir o site de documentação do MSE
about: Mostrar informações sobre este aplicativo
# new set window
@@ -695,6 +697,8 @@ label:
installer status: Status:
no version: -
load image: Clique duas vezes para carregar uma imagem
# print dialog
put space between cards: Colocar um espaço entre os Cards?
spacing print: Espaçamento entre Cards em milímetros
@@ -763,6 +767,10 @@ button:
if internet connection exists: Se a conexão da internet existir
never: Nunca
internal image extension: Armazenar imagens internamente com extensão de arquivo
allow image download:
Permita o download das imagens.
Isso pode aumentar significativamente
a duração das importações de JSON e CSV.
# column select
move up: Mover para &Cima
@@ -1053,6 +1061,12 @@ error:
bulk modify nothing: Nenhum Card foi modificado.
bulk modify success: Número de Cards modificados com sucesso: %s
# web request
web request failed: Falha na solicitação da Web
web request cant create: Não foi possível criar o objeto de solicitação da Web
web request corrupted: Download concluído com sucesso, mas os dados estão corrompidos
web request unsupported format: Download concluído com sucesso, mas os dados estão em um formato não compatível
# stats panel
dimension not found: Não há estatísticas de dimensões '%s'
+12
View File
@@ -128,6 +128,8 @@ menu:
help: Помощь
index: Основное... F1
website: Сайт проекта MSE...
#TODO: Localize
documentation: &Documentation...
about: О Magic Set Editor...
## symbol editor menus
@@ -272,6 +274,8 @@ help:
#TODO: Localize Section
index: Open the index
website: Open the MSE website
#TODO: Localize
documentation: Open the MSE documentation website
about: Show information about this app
# new set window
@@ -766,6 +770,9 @@ label:
installer status: Статус:
no version: -
#TODO: Localize
load image: Double click to load image
# print dialog
#TODO: Localize section
put space between cards: Put space between cards?
@@ -843,6 +850,11 @@ button:
never: Никогда
#TODO: Localize
internal image extension: Store images internally with file extension
#TODO: Localize
allow image download:
Allow downloading of images.
This may significantly increase the
duration of JSON and CSV imports.
# column select
move up: Выше
+14 -1
View File
@@ -22,9 +22,13 @@ Font::Font()
, scale_down_to(100000)
, max_stretch(1.0)
, color(Color(0,0,0))
, shadow_color(Color(0,0,0))
, shadow_displacement_x(0)
, shadow_displacement_y(0)
, shadow_blur(0)
, stroke_color(Color(0,0,0))
, stroke_radius(0)
, stroke_blur(0)
, separator_color(Color(0,0,0,128))
, flags(FONT_NORMAL)
{}
@@ -96,6 +100,9 @@ bool Font::update(Context& ctx) {
changes |= shadow_displacement_x.update(ctx);
changes |= shadow_displacement_y.update(ctx);
changes |= shadow_blur .update(ctx);
changes |= stroke_color .update(ctx);
changes |= stroke_radius .update(ctx);
changes |= stroke_blur .update(ctx);
flags = (flags & ~FONT_BOLD & ~FONT_ITALIC)
| (weight() == _("bold") ? FONT_BOLD : FONT_NORMAL)
| (style() == _("italic") ? FONT_ITALIC : FONT_NORMAL);
@@ -114,6 +121,9 @@ void Font::initDependencies(Context& ctx, const Dependency& dep) const {
shadow_displacement_x.initDependencies(ctx, dep);
shadow_displacement_y.initDependencies(ctx, dep);
shadow_blur .initDependencies(ctx, dep);
stroke_color .initDependencies(ctx, dep);
stroke_blur .initDependencies(ctx, dep);
stroke_radius .initDependencies(ctx, dep);
}
FontP Font::make(int add_flags, bool add_underline, bool add_strikethrough, String const* other_family, Color const* other_color, double const* other_size) const {
@@ -204,9 +214,12 @@ IMPLEMENT_REFLECTION_NO_SCRIPT(Font) {
REFLECT(color);
REFLECT(scale_down_to);
REFLECT(max_stretch);
REFLECT(shadow_color);
REFLECT(shadow_displacement_x);
REFLECT(shadow_displacement_y);
REFLECT(shadow_color);
REFLECT(shadow_blur);
REFLECT(stroke_color);
REFLECT(stroke_radius);
REFLECT(stroke_blur);
REFLECT(separator_color);
}
+17 -10
View File
@@ -29,11 +29,11 @@ enum FontFlags
, FONT_CODE_OPER = 0x80 // syntax highlighting
};
/// A font for rendering text
/// A reference to a font for rendering text
/** Contains additional information about scaling, color and shadow */
class Font : public IntrusivePtrBase<Font> {
public:
Scriptable<String> name; ///< Name of the font
Scriptable<String> name; ///< Name of the referenced font
Scriptable<String> italic_name; ///< Font name for italic text (optional)
Scriptable<double> size; ///< Size of the font
Scriptable<String> weight, style; ///< Weight and style of the font (bold/italic)
@@ -42,10 +42,13 @@ public:
double scale_down_to; ///< Smallest size to scale down to
double max_stretch; ///< How much should the font be stretched before scaling down?
Scriptable<Color> color; ///< Color to use
Scriptable<Color> shadow_color; ///< Color for shadow
Scriptable<double> shadow_displacement_x;///< Position of the shadow
Scriptable<double> shadow_displacement_y;///< Position of the shadow
Scriptable<double> shadow_blur; ///< Blur radius of the shadow
Scriptable<Color> shadow_color; ///< Color for the shadow
Scriptable<double> shadow_displacement_x;///< Offset of the shadow in pixels, for a font size of 15
Scriptable<double> shadow_displacement_y;///< Offset of the shadow in pixels, for a font size of 15
Scriptable<double> shadow_blur; ///< Blur radius of the shadow in pixels, for a font size of 15
Scriptable<Color> stroke_color; ///< Color for the stroke
Scriptable<double> stroke_radius; ///< Thickness of the stroke in pixels, for a font size of 15
Scriptable<double> stroke_blur; ///< Blur radius of the stroke in pixels, for a font size of 15
Color separator_color; ///< Color for <sep> text
int flags; ///< FontFlags for this font
@@ -62,15 +65,19 @@ public:
/// Add the given dependency to the dependent_scripts list for the variables this font depends on
void initDependencies(Context&, const Dependency&) const;
/// Does this font have a shadow?
/// Add a shadow under the text?
inline bool hasShadow() const {
return shadow_displacement_x != 0.0 || shadow_displacement_y != 0.0;
return (!almost_equal(shadow_blur(), 0.0) || !almost_equal(shadow_displacement_x(), 0.0) || !almost_equal(shadow_displacement_y(), 0.0)) && shadow_color().Alpha() != 0;
}
/// Add a stroke effect around the text?
inline bool hasStroke() const {
return (!almost_equal(stroke_blur(), 0.0) || !almost_equal(stroke_radius(), 0.0)) && stroke_color().Alpha() != 0;
}
/// Add style to a font, and optionally change the font family, color and size
/// Add style, and optionally change the font family, color and size
FontP make(int add_flags, bool add_underline, bool add_strikethrough, String const* other_family, Color const* other_color, double const* other_size) const;
/// Convert this font to a wxFont
/// Convert this font reference to a wxFont
wxFont toWxFont(double scale) const;
private:
+161 -24
View File
@@ -7,6 +7,7 @@
// ----------------------------------------------------------------------------- : Includes
#include <util/prec.hpp>
#include <gfx/gfx.hpp>
#include <data/symbol_font.hpp>
#include <data/stylesheet.hpp>
#include <util/dynamic_arg.hpp>
@@ -269,31 +270,131 @@ SymbolInFont* SymbolFont::defaultSymbol() const {
// ----------------------------------------------------------------------------- : SymbolFont : drawing
void SymbolFont::draw(RotatedDC& dc, Context& ctx, const RealRect& rect, double font_size, const Alignment& align, const String& text) {
void SymbolFont::draw(RotatedDC& dc, Context& ctx, const RealRect& rect, double scale, const SymbolFontRef& font, const String& text) {
SplitSymbols symbols;
update(ctx);
split(text, symbols);
draw(dc, rect, font_size, align, symbols);
draw(dc, rect, scale, font, symbols);
}
void SymbolFont::draw(RotatedDC& dc, RealRect rect, double font_size, const Alignment& align, const SplitSymbols& text) {
void SymbolFont::draw(RotatedDC& dc, RealRect rect, double scale, const SymbolFontRef& font, const SplitSymbols& text) {
double stretch = dc.getStretch();
FOR_EACH_CONST(sym, text) {
RealSize size = dc.trInvS(symbolSize(dc.trS(font_size), sym));
RealSize size = dc.trInvS(symbolSize(dc.trS(scale * font.size), sym));
size = RealSize(size.width*stretch, size.height);
RealRect sym_rect = split_left(rect, size);
drawSymbol(dc, sym_rect, font_size, align, *sym.symbol, sym.draw_text);
// TODO: draw all stroke effects and shadows first, then draw all symbols
drawSymbol(dc, sym_rect, scale, font, *sym.symbol, sym.draw_text, stretch);
}
}
void SymbolFont::drawSymbol(RotatedDC& dc, RealRect sym_rect, double font_size, const Alignment& align, SymbolInFont& sym, const String& text) {
// 1. draw symbol
// find bitmap
Bitmap bmp = sym.getBitmap(*this, dc.trS(font_size));
// draw aligned in the rectangle
void SymbolFont::drawSymbol(RotatedDC& dc, RealRect sym_rect, double scale, const SymbolFontRef& font, SymbolInFont& sym, const String& text, double stretch) {
// 1. find bitmap and pos
double font_size = scale * font.size();
double font_size_ext = dc.trS(scale * font.size());
Bitmap bmp = sym.getBitmap(*this, font_size_ext);
if (!almost_equal(stretch, 1.0)) {
bmp = Bitmap(resample(bmp.ConvertToImage(), bmp.GetWidth() * stretch, bmp.GetHeight()));
}
RealSize bmp_size = dc.trInvS(RealSize(bmp));
RealPoint bmp_pos = align_in_rect(align, bmp_size, sym_rect);
RealPoint bmp_pos = align_in_rect(font.alignment(), bmp_size, sym_rect);
// 2. draw potential stroke or shadow
double s_scale = font_size_ext / 15.;
if (font.hasStroke()) {
// add margin
Image img = bmp.ConvertToImage();
if (!img.HasAlpha()) set_alpha(img, 0);
int blur_radius = lround(font.stroke_blur() * s_scale);
int stroke_radius = lround(font.stroke_radius() * s_scale);
int margin = blur_radius + stroke_radius;
int s_width = img.GetWidth() + 2 * margin, s_height = img.GetHeight() + 2 * margin;
int x_end = s_width - margin;
int y_end = s_height - margin;
wxImage s_img(s_width, s_height, false);
s_img.InitAlpha();
// convert to stroke color
Byte* s_data = s_img.GetData();
Byte* s_alpha = s_img.GetAlpha(), *alpha = img.GetAlpha();
Color color = font.stroke_color();
unsigned char r = color.Red();
unsigned char g = color.Green();
unsigned char b = color.Blue();
unsigned char a = color.Alpha();
for (int y = 0 ; y < s_height ; ++y) {
for (int x = 0 ; x < s_width ; ++x) {
s_data[0] = r;
s_data[1] = g;
s_data[2] = b;
s_data += 3;
if (margin <= x && x < x_end && margin <= y && y < y_end) {
s_alpha[0] = alpha[0] * a / 255;
alpha += 1;
}
else {
s_alpha[0] = 0;
}
s_alpha += 1;
}
}
// add stroke effect
for (int i = 0 ; i < stroke_radius ; ++i) {
thicken_image_alpha(s_img, 1);
}
// add blur
for (int i = 0 ; i < blur_radius ; ++i) {
blur_image_alpha(s_img, 3);
}
// draw
RealSize s_size = dc.trInvS(RealSize(s_img));
RealPoint s_pos(bmp_pos.x - (s_size.width - bmp_size.width)/2, bmp_pos.y - (s_size.height - bmp_size.height)/2);
dc.DrawImage(s_img, s_pos);
}
else if (font.hasShadow()) {
// add margin
Image img = bmp.ConvertToImage();
if (!img.HasAlpha()) set_alpha(img, 0);
int margin = lround(font.shadow_blur() * s_scale);
int s_width = img.GetWidth() + 2 * margin, s_height = img.GetHeight() + 2 * margin;
int x_end = s_width - margin;
int y_end = s_height - margin;
wxImage s_img(s_width, s_height, false);
s_img.InitAlpha();
// convert to shadow color
Byte* s_data = s_img.GetData();
Byte* s_alpha = s_img.GetAlpha(), *alpha = img.GetAlpha();
Color color = font.shadow_color();
unsigned char r = color.Red();
unsigned char g = color.Green();
unsigned char b = color.Blue();
unsigned char a = color.Alpha();
for (int y = 0 ; y < s_height ; ++y) {
for (int x = 0 ; x < s_width ; ++x) {
s_data[0] = r;
s_data[1] = g;
s_data[2] = b;
s_data += 3;
if (margin <= x && x < x_end && margin <= y && y < y_end) {
s_alpha[0] = alpha[0] * a / 255;
alpha += 1;
}
else {
s_alpha[0] = 0;
}
s_alpha += 1;
}
}
// add blur
for (int i = 0 ; i < margin ; ++i) {
blur_image_alpha(s_img, 3);
}
// draw
RealSize s_size = dc.trInvS(RealSize(s_img));
RealPoint s_pos(bmp_pos.x - (s_size.width - bmp_size.width)/2, bmp_pos.y - (s_size.height - bmp_size.height)/2);
dc.DrawImage(s_img, s_pos + RealPoint(font.shadow_displacement_x(), font.shadow_displacement_y()) * scale);
}
// 3. draw bitmap
dc.DrawBitmap(bmp, bmp_pos);
// 2. draw text
// 4. draw text
if (text.empty() || !sym.text_font) return;
// only use the bitmap rectangle
sym_rect = RealRect(bmp_pos, bmp_size);
@@ -304,7 +405,7 @@ void SymbolFont::drawSymbol(RotatedDC& dc, RealRect sym_rect, double font_size,
sym_rect.height -= font_size * (sym.text_margin_top + sym.text_margin_bottom);
// setup text, shrink it
double size = font_size * sym.text_font->size;
double stretch = 1.0;
double text_stretch = 1.0;
RealSize ts;
while (true) {
if (size <= 0) return; // text too small
@@ -314,7 +415,7 @@ void SymbolFont::drawSymbol(RotatedDC& dc, RealRect sym_rect, double font_size,
if (ts.width <= sym_rect.width) {
break; // text fits
} else if (ts.width * sym.text_font->max_stretch <= sym_rect.width) {
stretch = sym_rect.width / ts.width;
text_stretch = sym_rect.width / ts.width;
ts.width = sym_rect.width; // for alignment
break;
}
@@ -325,7 +426,7 @@ void SymbolFont::drawSymbol(RotatedDC& dc, RealRect sym_rect, double font_size,
// align text
RealPoint text_pos = align_in_rect(sym.text_alignment, ts, sym_rect);
// draw text
dc.DrawTextWithShadow(text, *sym.text_font, text_pos, font_size, stretch);
dc.DrawTextWithShadowOrStroke(text, *sym.text_font, text_pos, font_size, text_stretch);
}
Image SymbolFont::getImage(double font_size, const DrawableSymbol& sym) {
@@ -345,7 +446,7 @@ Image SymbolFont::getImage(double font_size, const DrawableSymbol& sym) {
sym_rect.height -= font_size * (sym.symbol->text_margin_top + sym.symbol->text_margin_bottom);
// setup text, shrink it
double size = font_size * sym.symbol->text_font->size;
double stretch = 1.0;
double text_stretch = 1.0;
RealSize ts;
while (true) {
if (size <= 0) return sym.symbol->getImage(*this, font_size); // text too small
@@ -355,7 +456,7 @@ Image SymbolFont::getImage(double font_size, const DrawableSymbol& sym) {
if (ts.width <= sym_rect.width) {
break; // text fits
} else if (ts.width * sym.symbol->text_font->max_stretch <= sym_rect.width) {
stretch = sym_rect.width / ts.width;
text_stretch = sym_rect.width / ts.width;
ts.width = sym_rect.width; // for alignment
break;
}
@@ -366,7 +467,7 @@ Image SymbolFont::getImage(double font_size, const DrawableSymbol& sym) {
// align text
RealPoint text_pos = align_in_rect(sym.symbol->text_alignment, ts, sym_rect);
// draw text
rdc.DrawTextWithShadow(sym.draw_text, *sym.symbol->text_font, text_pos, font_size, stretch);
rdc.DrawTextWithShadowOrStroke(sym.draw_text, *sym.symbol->text_font, text_pos, font_size, text_stretch);
// done
dc.SelectObject(wxNullBitmap);
return bmp.ConvertToImage();
@@ -568,7 +669,16 @@ void after_reading(InsertSymbolMenu& m, Version ver) {
SymbolFontRef::SymbolFontRef()
: size(12)
, scale_down_to(1)
, underline(false)
, strikethrough(false)
, alignment(ALIGN_MIDDLE_CENTER)
, shadow_color(Color(0,0,0))
, shadow_displacement_x(0)
, shadow_displacement_y(0)
, shadow_blur(0)
, stroke_color(Color(0,0,0))
, stroke_radius(0)
, stroke_blur(0)
{}
bool SymbolFontRef::valid() const {
@@ -584,14 +694,32 @@ bool SymbolFontRef::update(Context& ctx) {
} else if (!font) {
loadFont(ctx);
}
changes |= size.update(ctx);
changes |= alignment.update(ctx);
changes |= size .update(ctx);
changes |= underline .update(ctx);
changes |= strikethrough .update(ctx);
changes |= alignment .update(ctx);
changes |= shadow_color .update(ctx);
changes |= shadow_displacement_x.update(ctx);
changes |= shadow_displacement_y.update(ctx);
changes |= shadow_blur .update(ctx);
changes |= stroke_color .update(ctx);
changes |= stroke_radius .update(ctx);
changes |= stroke_blur .update(ctx);
return changes;
}
void SymbolFontRef::initDependencies(Context& ctx, const Dependency& dep) const {
name.initDependencies(ctx, dep);
size.initDependencies(ctx, dep);
alignment.initDependencies(ctx, dep);
name .initDependencies(ctx, dep);
size .initDependencies(ctx, dep);
underline .initDependencies(ctx, dep);
strikethrough .initDependencies(ctx, dep);
alignment .initDependencies(ctx, dep);
shadow_color .initDependencies(ctx, dep);
shadow_displacement_x.initDependencies(ctx, dep);
shadow_displacement_y.initDependencies(ctx, dep);
shadow_blur .initDependencies(ctx, dep);
stroke_color .initDependencies(ctx, dep);
stroke_radius .initDependencies(ctx, dep);
stroke_blur .initDependencies(ctx, dep);
}
void SymbolFontRef::loadFont(Context& ctx) {
@@ -614,5 +742,14 @@ IMPLEMENT_REFLECTION(SymbolFontRef) {
REFLECT(name);
REFLECT(size);
REFLECT(scale_down_to);
REFLECT(underline);
REFLECT(strikethrough);
REFLECT(alignment);
REFLECT(shadow_color);
REFLECT(shadow_displacement_x);
REFLECT(shadow_displacement_y);
REFLECT(shadow_blur);
REFLECT(stroke_color);
REFLECT(stroke_radius);
REFLECT(stroke_blur);
}
+26 -7
View File
@@ -20,6 +20,7 @@ DECLARE_POINTER_TYPE(SymbolFont);
DECLARE_POINTER_TYPE(SymbolInFont);
DECLARE_POINTER_TYPE(InsertSymbolMenu);
class RotatedDC;
class SymbolFontRef;
struct CharInfo;
// ----------------------------------------------------------------------------- : SymbolFont
@@ -56,12 +57,12 @@ public:
size_t recognizePrefix(const String& text, size_t start) const;
/// Draw a piece of text
void draw(RotatedDC& dc, Context& ctx, const RealRect& rect, double font_size, const Alignment& align, const String& text);
void draw(RotatedDC& dc, Context& ctx, const RealRect& rect, double scale, const SymbolFontRef& font, const String& text);
/// Get information on characters in a string
void getCharInfo(RotatedDC& dc, Context& ctx, double font_size, const String& text, vector<CharInfo>& out);
/// Draw a piece of text prepared using split
void draw(RotatedDC& dc, RealRect rect, double font_size, const Alignment& align, const SplitSymbols& text);
void draw(RotatedDC& dc, RealRect rect, double scale, const SymbolFontRef& font, const SplitSymbols& text);
/// Get information on characters in a string
void getCharInfo(RotatedDC& dc, double font_size, const SplitSymbols& text, vector<CharInfo>& out);
@@ -99,7 +100,7 @@ private:
SymbolInFont* defaultSymbol() const;
/// Draws a single symbol inside the given rectangle
void drawSymbol (RotatedDC& dc, RealRect sym_rect, double font_size, const Alignment& align, SymbolInFont& sym, const String& text);
void drawSymbol(RotatedDC& dc, RealRect sym_rect, double scale, const SymbolFontRef& font, SymbolInFont& sym, const String& text, double stretch);
/// Size of a single symbol, including spacing
RealSize symbolSize (double font_size, const DrawableSymbol& sym);
@@ -158,14 +159,32 @@ public:
bool update(Context& ctx);
void initDependencies(Context&, const Dependency&) const;
/// Is a font loaded?
/// Is the referenced symbol font loaded?
bool valid() const;
Scriptable<String> name; ///< Font package name, can be changed with script
Scriptable<String> name; ///< The referenced symbol font's package name (folder name)
Scriptable<double> size; ///< Size of the font
double scale_down_to; ///< Mimumum size of the font
double scale_down_to; ///< Minimum size of the font
Scriptable<bool> underline; ///< Underlined?
Scriptable<bool> strikethrough; ///< Struck through?
Scriptable<Alignment> alignment; ///< Alignment of symbols in a line of text
SymbolFontP font; ///< The font, if it is loaded
Scriptable<Color> shadow_color; ///< Color for the shadow
Scriptable<double> shadow_displacement_x;///< Offset of the shadow in pixels, for a font size of 15
Scriptable<double> shadow_displacement_y;///< Offset of the shadow in pixels, for a font size of 15
Scriptable<double> shadow_blur; ///< Blur radius of the shadow in pixels, for a font size of 15
Scriptable<Color> stroke_color; ///< Color for the stroke
Scriptable<double> stroke_radius; ///< Thickness of the stroke in pixels, for a font size of 15
Scriptable<double> stroke_blur; ///< Blur radius of the stroke in pixels, for a font size of 15
SymbolFontP font; ///< The symbol font this is referencing, if it is loaded
/// Add a shadow under symbols?
inline bool hasShadow() const {
return (!almost_equal(shadow_blur(), 0.0) || !almost_equal(shadow_displacement_x(), 0.0) || !almost_equal(shadow_displacement_y(), 0.0)) && shadow_color().Alpha() != 0;
}
/// Add a stroke effect around symbols?
inline bool hasStroke() const {
return (!almost_equal(stroke_blur(), 0.0) || !almost_equal(stroke_radius(), 0.0)) && stroke_color().Alpha() != 0;
}
private:
DECLARE_REFLECTION();
+17 -1
View File
@@ -53,11 +53,15 @@ void sharp_resample_and_clip(const Image& img_in, Image& img_out, wxRect rect, i
* rect = rectangle to draw in (a rectangle somewhere around pos)
* stretch = amount to stretch in the direction of the text after drawing
*/
void draw_resampled_text(DC& dc, const RealPoint& pos, const RealRect& rect, double stretch, Radians angle, Color color, const String& text, int blur_radius = 0, int repeat = 1);
void draw_resampled_text(DC& dc, const String& text, const RealPoint& pos, const RealRect& rect, Radians angle, Color color, int blur_radius = 0, Color stroke_color = Color(0,0,0), int stroke_radius = 0, double stretch = 1.0);
// scaling factor to use when drawing resampled text
extern const int text_scaling;
// Downsamples the red channel of the input image to the alpha channel of the output image
// img_in must be text_scaling times as large as img_out
void downsample_to_alpha(Bitmap& bmp_in, Image& img_out);
// ----------------------------------------------------------------------------- : Image rotation
/// Rotates an image counter clockwise
@@ -92,6 +96,18 @@ void saturate(Image& image, double amount);
/// Invert the colors in an image
void invert(Image& img);
// Blur the alpha of a pixel
Byte blur_pixel_alpha(Byte* in, int x, int y, int width, int height, int center_weight = 2);
// Blur the alpha channel of an image
void blur_image_alpha(Image& img, int center_weight = 2);
// Thicken the alpha of a pixel
Byte thicken_pixel_alpha_3x3(std::vector<Byte> in, int i, int x, int y, int width, int height);
Byte thicken_pixel_alpha_5x5(std::vector<Byte> in, int i, int x, int y, int width, int height);
Byte thicken_pixel_alpha_7x7(std::vector<Byte> in, int i, int x, int y, int width, int height);
// Thicken the alpha channel of an image
void thicken_image_alpha(Image& img, int radius);
// ----------------------------------------------------------------------------- : Combining
/// Ways in which images can be combined, similair to what Photoshop supports
+291
View File
@@ -9,6 +9,9 @@
#include <util/prec.hpp>
#include <gfx/gfx.hpp>
#include <util/error.hpp>
#if defined(__WXMSW__) && wxUSE_WXDIB
#include <wx/msw/dib.h>
#endif
// ----------------------------------------------------------------------------- : Saturation
@@ -71,6 +74,294 @@ void invert(Image& img) {
}
}
// ----------------------------------------------------------------------------- : Blurring
Byte blur_pixel_alpha(Byte* in, int x, int y, int width, int height, int center_weight) {
return ( center_weight * in[0] + // center
(x == 0 ? in[0] : in[-1]) + // left
(y == 0 ? in[0] : in[-width]) + // up
(x == width - 1 ? in[0] : in[1]) + // right
(y == height - 1 ? in[0] : in[width]) // down
) / (4 + center_weight);
}
void blur_image_alpha(Image& img, int center_weight) {
if (!img.HasAlpha()) return;
int width = img.GetWidth(), height = img.GetHeight();
Byte* data = img.GetAlpha();
for (int y = 0 ; y < height ; ++y) {
for (int x = 0 ; x < width ; ++x) {
*data = blur_pixel_alpha(data, x, y, width, height, center_weight);
++data;
}
}
}
// ----------------------------------------------------------------------------- : Thickening
Byte thicken_pixel_alpha_7x7(std::vector<unsigned char> in, int i, int x, int y, int width, int height) {
Byte result = in[i];
Byte diag = in[i];
Byte corner = in[i];
if (x > 2) {
if (y > 1) corner = max(corner, in[i -3 -2*width]);
if (y > 0) diag = max(diag , in[i -3 -1*width]);
result = max(result, in[i -3 ]);
if (y < height - 1) diag = max(diag , in[i -3 +1*width]);
if (y < height - 2) corner = max(corner, in[i -3 +2*width]);
}
if (x > 1) {
if (y > 2) corner = max(corner, in[i -2 -3*width]);
if (y > 1) result = max(result, in[i -2 -2*width]);
if (y > 0) result = max(result, in[i -2 -1*width]);
result = max(result, in[i -2 ]);
if (y < height - 1) result = max(result, in[i -2 +1*width]);
if (y < height - 2) result = max(result, in[i -2 +2*width]);
if (y < height - 3) corner = max(corner, in[i -2 +3*width]);
}
if (x > 0) {
if (y > 2) diag = max(diag , in[i -1 -3*width]);
if (y > 1) result = max(result, in[i -1 -2*width]);
if (y > 0) result = max(result, in[i -1 -1*width]);
result = max(result, in[i -1 ]);
if (y < height - 1) result = max(result, in[i -1 +1*width]);
if (y < height - 2) result = max(result, in[i -1 +2*width]);
if (y < height - 3) diag = max(diag , in[i -1 +3*width]);
}
if (y > 2) result = max(result, in[i -3*width]);
if (y > 1) result = max(result, in[i -2*width]);
if (y > 0) result = max(result, in[i -1*width]);
if (y < height - 1) result = max(result, in[i +1*width]);
if (y < height - 2) result = max(result, in[i +2*width]);
if (y < height - 3) result = max(result, in[i +3*width]);
if (x < width - 1) {
if (y > 2) diag = max(diag , in[i +1 -3*width]);
if (y > 1) result = max(result, in[i +1 -2*width]);
if (y > 0) result = max(result, in[i +1 -1*width]);
result = max(result, in[i +1 ]);
if (y < height - 1) result = max(result, in[i +1 +1*width]);
if (y < height - 2) result = max(result, in[i +1 +2*width]);
if (y < height - 3) diag = max(diag , in[i +1 +3*width]);
}
if (x < width - 2) {
if (y > 2) corner = max(corner, in[i +2 -3*width]);
if (y > 1) result = max(result, in[i +2 -2*width]);
if (y > 0) result = max(result, in[i +2 -1*width]);
result = max(result, in[i +2 ]);
if (y < height - 1) result = max(result, in[i +2 +1*width]);
if (y < height - 2) result = max(result, in[i +2 +2*width]);
if (y < height - 3) corner = max(corner, in[i +2 +3*width]);
}
if (x < width - 3) {
if (y > 1) corner = max(corner, in[i +3 -2*width]);
if (y > 0) diag = max(diag , in[i +3 -1*width]);
result = max(result, in[i +3 ]);
if (y < height - 1) diag = max(diag , in[i +3 +1*width]);
if (y < height - 2) corner = max(corner, in[i +3 +2*width]);
}
if (diag > result) result = (Byte)((4 * (int)diag + (int)result) / 5);
if (corner > result) result = (Byte)((2 * (int)corner + 3 * (int)result) / 5);
return result;
}
Byte thicken_pixel_alpha_5x5(std::vector<unsigned char> in, int i, int x, int y, int width, int height) {
Byte result = in[i];
Byte diag = in[i];
if (x > 1) {
if (y > 0) diag = max(diag , in[i -2 -1*width]);
result = max(result, in[i -2 ]);
if (y < height - 1) diag = max(diag , in[i -2 +1*width]);
}
if (x > 0) {
if (y > 1) diag = max(diag , in[i -1 -2*width]);
if (y > 0) result = max(result, in[i -1 -1*width]);
result = max(result, in[i -1 ]);
if (y < height - 1) result = max(result, in[i -1 +1*width]);
if (y < height - 2) diag = max(diag , in[i -1 +2*width]);
}
if (y > 1) result = max(result, in[i -2*width]);
if (y > 0) result = max(result, in[i -1*width]);
if (y < height - 1) result = max(result, in[i +1*width]);
if (y < height - 2) result = max(result, in[i +2*width]);
if (x < width - 1) {
if (y > 1) diag = max(diag , in[i +1 -2*width]);
if (y > 0) result = max(result, in[i +1 -1*width]);
result = max(result, in[i +1 ]);
if (y < height - 1) result = max(result, in[i +1 +1*width]);
if (y < height - 2) diag = max(diag , in[i +1 +2*width]);
}
if (x < width - 2) {
if (y > 0) diag = max(diag , in[i +2 -1*width]);
result = max(result, in[i +2 ]);
if (y < height - 1) diag = max(diag , in[i +2 +1*width]);
}
if (diag > result) result = (Byte)((3 * (int)diag + (int)result) / 4);
return result;
}
Byte thicken_pixel_alpha_3x3(std::vector<unsigned char> in, int i, int x, int y, int width, int height) {
Byte result = in[i];
Byte diag = in[i];
if (x > 0) {
if (y > 0) diag = max(diag, in[i -1 -width]);
result = max(result, in[i -1 ]);
if (y < height - 1) diag = max(diag, in[i -1 +width]);
}
if (y > 0) result = max(result, in[i -width]);
if (y < height - 1) result = max(result, in[i +width]);
if (x < width - 1) {
if (y > 0) diag = max(diag, in[i +1 -width]);
result = max(result, in[i +1 ]);
if (y < height - 1) diag = max(diag, in[i +1 +width]);
}
if (diag > result) result = (Byte)((3*(int)diag + 2*(int)result) / 5);
return result;
}
void thicken_image_alpha(Image& img, int radius) {
if (!img.HasAlpha()) return;
int width = img.GetWidth(), height = img.GetHeight();
Byte* data = img.GetAlpha();
std::vector<Byte> copy(data, data + width * height);
int i = 0;
for (int y = 0 ; y < height ; ++y) {
for (int x = 0 ; x < width ; ++x) {
*data = radius == 1 ? thicken_pixel_alpha_3x3(copy, i, x, y, width, height) :
radius == 2 ? thicken_pixel_alpha_5x5(copy, i, x, y, width, height) :
thicken_pixel_alpha_7x7(copy, i, x, y, width, height) ;
++data;
++i;
}
}
}
// ----------------------------------------------------------------------------- : Resampled text
void downsample_to_alpha(Bitmap& bmp_in, Image& img_out) {
Byte* temp = nullptr;
#if defined(__WXMSW__) && wxUSE_WXDIB
wxDIB img_in(bmp_in);
if (!img_in.IsOk()) return;
// if text_scaling = 4, then the line always is dword aligned, so we need no adjusting
// we created a bitmap with depth 24, so that is what we should have here
if (img_in.GetDepth() != 24) throw InternalError(_("DIB has wrong bit depth"));
#else
Image img_in = bmp_in.ConvertToImage();
#endif
Byte* in = img_in.GetData();
Byte* out = img_in.GetData();
// scale in the x direction, this overwrites parts of the input image
if (img_in.GetWidth() == img_out.GetWidth() * text_scaling) {
// no stretching
int count = img_out.GetWidth() * img_in.GetHeight();
for (int i = 0 ; i < count ; ++i) {
int total = 0;
for (int j = 0 ; j < text_scaling ; ++j) {
total += in[3 * (j + text_scaling * i)];
}
out[i] = total / text_scaling;
}
} else {
// resample to buffer
temp = new Byte[img_out.GetWidth() * img_in.GetHeight()];
out = temp;
// custom stretch, see resample_image.cpp
const int shift = 32-12-8; // => max size = 4096, max alpha = 255
int w1 = img_in.GetWidth(), w2 = img_out.GetWidth(), h = img_in.GetHeight();
int out_fact = (w2 << shift) / w1; // how much to output for 256 input = 1 pixel
int out_rest = (w2 << shift) % w1;
// make the image 'bolder' to compensate for compressing it
int mul = 128 + min(256, 128*w1/(text_scaling*w2));
for (int y = 0 ; y < h ; ++y) {
int in_rem = out_fact + out_rest;
for (int x = 0 ; x < w2 ; ++x) {
int out_rem = 1 << shift;
int tot = 0;
while (out_rem >= in_rem) {
// eat a whole input pixel
tot += *in * in_rem;
out_rem -= in_rem;
in_rem = out_fact;
in += 3;
}
if (out_rem > 0) {
// eat a partial input pixel
tot += *in * out_rem;
in_rem -= out_rem;
}
// store
*out = top(((tot >> shift) * mul) >> 8);
out += 1;
}
}
in = temp;
}
// now scale in the y direction, and write to the output alpha
img_out.InitAlpha();
int line_size_in = img_out.GetWidth();
#if defined(__WXMSW__) && wxUSE_WXDIB
// DIBs are upside down
out = img_out.GetAlpha() + (img_out.GetHeight() - 1) * line_size_in;
int line_size_out = -line_size_in;
#else
out = img_out.GetAlpha();
int line_size_out = line_size_in;
#endif
int h = img_out.GetHeight();
if (img_in.GetHeight() == h * text_scaling) {
// no stretching
for (int y = 0 ; y < h ; ++y) {
for (int x = 0 ; x < line_size_in ; ++x) {
int total = 0;
for (int j = 0 ; j < text_scaling ; ++j) {
total += in[x + line_size_in * (j + text_scaling * y)];
}
out[x + line_size_out * y] = total / text_scaling;
}
}
} else {
const int shift = 32-12-8; // => max size = 4096, max alpha = 255
int h1 = img_in.GetHeight(), w = img_out.GetWidth();
int out_fact = (h << shift) / h1; // how much to output for 256 input = 1 pixel
int out_rest = (h << shift) % h1;
int mul = 128 + min(256, 128*h1/(text_scaling*h));
for (int x = 0 ; x < w ; ++x) {
int in_rem = out_fact + out_rest;
for (int y = 0 ; y < h ; ++y) {
int out_rem = 1 << shift;
int tot = 0;
while (out_rem >= in_rem) {
// eat a whole input pixel
tot += *in * in_rem;
out_rem -= in_rem;
in_rem = out_fact;
in += line_size_in;
}
if (out_rem > 0) {
// eat a partial input pixel
tot += *in * out_rem;
in_rem -= out_rem;
}
// store
*out = top(((tot >> shift) * mul) >> 8);
out += line_size_out;
}
in = in - h1 * line_size_in + 1;
out = out - h * line_size_out + 1;
}
}
delete[] temp;
}
// ----------------------------------------------------------------------------- : Coloring symbol images
RGB recolor(RGB x, RGB cr, RGB cg, RGB cb, RGB cw) {
+49 -164
View File
@@ -10,9 +10,6 @@
#include <gfx/gfx.hpp>
#include <util/error.hpp>
#include <gui/util.hpp> // clearDC_black
#if defined(__WXMSW__) && wxUSE_WXDIB
#include <wx/msw/dib.h>
#endif
void blur_image(const Image& img_in, Image& img_out);
@@ -21,189 +18,77 @@ void blur_image(const Image& img_in, Image& img_out);
// scaling factor to use when drawing resampled text
const int text_scaling = 4;
// Downsamples the red channel of the input image to the alpha channel of the output image
// img_in must be text_scaling times as large as img_out
void downsample_to_alpha(Bitmap& bmp_in, Image& img_out) {
Byte* temp = nullptr;
#if defined(__WXMSW__) && wxUSE_WXDIB
wxDIB img_in(bmp_in);
if (!img_in.IsOk()) return;
// if text_scaling = 4, then the line always is dword aligned, so we need no adjusting
// we created a bitmap with depth 24, so that is what we should have here
if (img_in.GetDepth() != 24) throw InternalError(_("DIB has wrong bit depth"));
#else
Image img_in = bmp_in.ConvertToImage();
#endif
Byte* in = img_in.GetData();
Byte* out = img_in.GetData();
// scale in the x direction, this overwrites parts of the input image
if (img_in.GetWidth() == img_out.GetWidth() * text_scaling) {
// no stretching
int count = img_out.GetWidth() * img_in.GetHeight();
for (int i = 0 ; i < count ; ++i) {
int total = 0;
for (int j = 0 ; j < text_scaling ; ++j) {
total += in[3 * (j + text_scaling * i)];
}
out[i] = total / text_scaling;
}
} else {
// resample to buffer
temp = new Byte[img_out.GetWidth() * img_in.GetHeight()];
out = temp;
// custom stretch, see resample_image.cpp
const int shift = 32-12-8; // => max size = 4096, max alpha = 255
int w1 = img_in.GetWidth(), w2 = img_out.GetWidth(), h = img_in.GetHeight();
int out_fact = (w2 << shift) / w1; // how much to output for 256 input = 1 pixel
int out_rest = (w2 << shift) % w1;
// make the image 'bolder' to compensate for compressing it
int mul = 128 + min(256, 128*w1/(text_scaling*w2));
for (int y = 0 ; y < h ; ++y) {
int in_rem = out_fact + out_rest;
for (int x = 0 ; x < w2 ; ++x) {
int out_rem = 1 << shift;
int tot = 0;
while (out_rem >= in_rem) {
// eat a whole input pixel
tot += *in * in_rem;
out_rem -= in_rem;
in_rem = out_fact;
in += 3;
}
if (out_rem > 0) {
// eat a partial input pixel
tot += *in * out_rem;
in_rem -= out_rem;
}
// store
*out = top(((tot >> shift) * mul) >> 8);
out += 1;
}
}
in = temp;
}
// now scale in the y direction, and write to the output alpha
img_out.InitAlpha();
int line_size_in = img_out.GetWidth();
#if defined(__WXMSW__) && wxUSE_WXDIB
// DIBs are upside down
out = img_out.GetAlpha() + (img_out.GetHeight() - 1) * line_size_in;
int line_size_out = -line_size_in;
#else
out = img_out.GetAlpha();
int line_size_out = line_size_in;
#endif
int h = img_out.GetHeight();
if (img_in.GetHeight() == h * text_scaling) {
// no stretching
for (int y = 0 ; y < h ; ++y) {
for (int x = 0 ; x < line_size_in ; ++x) {
int total = 0;
for (int j = 0 ; j < text_scaling ; ++j) {
total += in[x + line_size_in * (j + text_scaling * y)];
}
out[x + line_size_out * y] = total / text_scaling;
}
}
} else {
const int shift = 32-12-8; // => max size = 4096, max alpha = 255
int h1 = img_in.GetHeight(), w = img_out.GetWidth();
int out_fact = (h << shift) / h1; // how much to output for 256 input = 1 pixel
int out_rest = (h << shift) % h1;
int mul = 128 + min(256, 128*h1/(text_scaling*h));
for (int x = 0 ; x < w ; ++x) {
int in_rem = out_fact + out_rest;
for (int y = 0 ; y < h ; ++y) {
int out_rem = 1 << shift;
int tot = 0;
while (out_rem >= in_rem) {
// eat a whole input pixel
tot += *in * in_rem;
out_rem -= in_rem;
in_rem = out_fact;
in += line_size_in;
}
if (out_rem > 0) {
// eat a partial input pixel
tot += *in * out_rem;
in_rem -= out_rem;
}
// store
*out = top(((tot >> shift) * mul) >> 8);
out += line_size_out;
}
in = in - h1 * line_size_in + 1;
out = out - h * line_size_out + 1;
}
}
delete[] temp;
}
// simple blur
int blur_alpha_pixel(Byte* in, int x, int y, int width, int height) {
return (2 * ( in[0]) + // center
(x == 0 ? in[0] : in[-1]) + // left
(y == 0 ? in[0] : in[-width]) + // up
(x == width - 1 ? in[0] : in[1]) + // right
(y == height - 1 ? in[0] : in[width]) // down
) / 6;
}
// TODO: move me?
void blur_image_alpha(Image& img) {
int width = img.GetWidth(), height = img.GetHeight();
Byte* data = img.GetAlpha();
for (int y = 0 ; y < height ; ++y) {
for (int x = 0 ; x < width ; ++x) {
*data = blur_alpha_pixel(data, x, y, width, height);
++data;
}
}
}
// Draw text by first drawing it using a larger font and then downsampling it
// optionally rotated by an angle
void draw_resampled_text(DC& dc, const RealPoint& pos, const RealRect& rect, double stretch, Radians angle, Color color, const String& text, int blur_radius, int repeat) {
void draw_resampled_text(DC& dc, const String& text, const RealPoint& pos, const RealRect& rect, Radians angle, Color color, int blur_radius, Color stroke_color, int stroke_radius, double stretch) {
// transparent text can be ignored
if (color.Alpha() == 0) return;
// enlarge slightly; some fonts are larger then the GetTextExtent tells us (especially italic fonts)
int w = static_cast<int>(rect.width) + 3 + 2 * blur_radius, h = static_cast<int>(rect.height) + 1 + 2 * blur_radius;
int w = static_cast<int>(rect.width) + 4, h = static_cast<int>(rect.height) + 2;
// determine sub-pixel position
int xi = static_cast<int>(rect.x) - blur_radius / text_scaling,
yi = static_cast<int>(rect.y) - blur_radius / text_scaling;
int xi = static_cast<int>(rect.x),
yi = static_cast<int>(rect.y);
int xsub = static_cast<int>(text_scaling * (pos.x - xi)),
ysub = static_cast<int>(text_scaling * (pos.y - yi));
// draw text
Bitmap buffer(w * text_scaling, h * text_scaling, 24); // should be initialized to black
// draw text as mask (white text on black background)
Bitmap buffer(w * text_scaling, h * text_scaling, 24);
wxMemoryDC mdc;
mdc.SelectObject(buffer);
clearDC_black(mdc);
// now draw the text
mdc.SetFont(dc.GetFont());
mdc.SetTextForeground(*wxWHITE);
mdc.DrawRotatedText(text, xsub, ysub, rad_to_deg(angle));
// get image
mdc.SelectObject(wxNullBitmap);
// step 2. sample down
// downsample
double ca = fabs(cos(angle)), sa = fabs(sin(angle));
w += int(w * (stretch - 1) * ca); // GCC makes annoying conversion warnings if *= is used here.
h += int(h * (stretch - 1) * sa);
Image img_small(w, h, false);
fill_image(img_small, color);
downsample_to_alpha(buffer, img_small);
// multiply alpha
Image img(w, h, false);
downsample_to_alpha(buffer, img);
// if there is no stroke effect, just add blur and draw
if (stroke_radius == 0) {
if (color.Alpha() != 255) {
set_alpha(img_small, color.Alpha() / 255.);
set_alpha(img, color.Alpha() / 255.);
}
// blur
if (blur_radius > 0) {
Image s_img(w + 2*blur_radius, h + 2*blur_radius);
set_alpha(s_img, 0);
s_img.Paste(img, blur_radius, blur_radius, wxIMAGE_ALPHA_BLEND_COMPOSE);
for (int i = 0 ; i < blur_radius ; ++i) {
blur_image_alpha(img_small);
blur_image_alpha(s_img, 3);
}
// step 3. draw to dc
for (int i = 0 ; i < repeat ; ++i) {
dc.DrawBitmap(img_small, xi, yi);
fill_image(s_img, color);
dc.DrawBitmap(s_img, xi-blur_radius, yi-blur_radius);
}
else {
fill_image(img, color);
dc.DrawBitmap(img, xi, yi);
}
}
// otherwise add stroke effect
else {
int radius = blur_radius + stroke_radius;
Image s_img(w + 2*radius, h + 2*radius);
set_alpha(s_img, 0);
s_img.Paste(img, radius, radius, wxIMAGE_ALPHA_BLEND_COMPOSE);
for (int i = 0 ; i < stroke_radius ; ++i) {
thicken_image_alpha(s_img, 1);
}
for (int i = 0 ; i < blur_radius ; ++i) {
blur_image_alpha(s_img, 3);
}
if (stroke_color.Alpha() != 255) {
set_alpha(s_img, stroke_color.Alpha() / 255.);
}
fill_image(s_img, stroke_color);
if (stroke_color != color) {
fill_image(img, color);
if (color.Alpha() != 255) {
set_alpha(img, color.Alpha() / 255.);
}
s_img.Paste(img, radius, radius, wxIMAGE_ALPHA_BLEND_COMPOSE);
}
dc.DrawBitmap(s_img, xi-radius, yi-radius);
}
}
+1 -1
View File
@@ -27,7 +27,7 @@ void FontTextElement::draw(RotatedDC& dc, double scale, const RealRect& rect, co
margin = RealSize(1., 0);
}
dc.SetFont(*font, scale);
dc.DrawTextWithShadow(text, *font, rect.position() + margin);
dc.DrawTextWithShadowOrStroke(text, *font, rect.position() + margin);
if (native_look) font->color = font_color;
}
+1 -1
View File
@@ -15,7 +15,7 @@
void SymbolTextElement::draw(RotatedDC& dc, double scale, const RealRect& rect, const double* xs, DrawWhat what, size_t start, size_t end, bool native_look) const {
if (!(what & DRAW_NORMAL)) return;
if (font.font) {
font.font->draw(dc, ctx, rect, font.size * scale, font.alignment, content.substr(start - this->start, end-start));
font.font->draw(dc, ctx, rect, scale, font, content.substr(start - this->start, end-start));
}
}
+1 -1
View File
@@ -117,7 +117,7 @@ void draw_choice_viewer(RotatedDC& dc, ValueViewer& viewer, ChoiceStyle& style,
dc.SetFont(font, 1.0);
RealSize size = dc.GetTextExtent(text);
RealPoint text_pos = align_in_rect(text_align, size, dc.getInternalRect()) + RealSize(margin, 0);
dc.DrawTextWithShadow(text, font, text_pos);
dc.DrawTextWithShadowOrStroke(text, font, text_pos);
if (viewer.nativeLook()) font.color = font_color;
}
}
+2 -6
View File
@@ -119,15 +119,11 @@ Bitmap ImageValueViewer::imagePlaceholder(const Rotation& rot, UInt w, UInt h, c
// only when in editor mode
for (UInt size = 12 ; size > 2 ; --size) {
dc.SetFont(wxFont(size, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL));
RealSize rs = dc.GetTextExtent(_("double click to load image"));
RealSize rs = dc.GetTextExtent(_LABEL_("load image"));
if (rs.width <= w - 10 && rs.height < h - 10) {
// text fits
RealPoint pos = align_in_rect(ALIGN_MIDDLE_CENTER, rs, rect);
bool black_on_white = !default_image.Ok() || very_light(default_image);
dc.SetTextForeground(black_on_white ? *wxWHITE : *wxBLACK);
dc.DrawText(_("double click to load image"), pos, 2, 4); // blurred
dc.SetTextForeground(black_on_white ? *wxBLACK : *wxWHITE);
dc.DrawText(_("double click to load image"), pos);
dc.DrawText(_LABEL_("load image"), pos, Color(255,255,255), 2, Color(0,0,0), 3); // stroked
break;
}
}
+1 -1
View File
@@ -88,7 +88,7 @@ void MultipleChoiceValueViewer::drawChoice(RotatedDC& dc, RealPoint& pos, const
dc.SetFont(font,1);
RealSize text_size = dc.GetTextExtent(text);
RealPoint text_pos = align_in_rect(ALIGN_MIDDLE_LEFT, text_size, RealRect(pos.x + size.width + 1, pos.y, 0,size.height)) + margin;
dc.DrawTextWithShadow(text, font, text_pos);
dc.DrawTextWithShadowOrStroke(text, font, text_pos);
size = add_horizontal(size, text_size);
if (nativeLook()) font.color = font_color;
}
+1 -1
View File
@@ -77,6 +77,6 @@ void PackageChoiceValueViewer::draw(RotatedDC& dc) {
}
dc.SetFont(font, 1.0);
RealPoint pos = align_in_rect(ALIGN_MIDDLE_LEFT, RealSize(0, dc.GetCharHeight()), dc.getInternalRect()) + RealSize(17., 0) + margin;
dc.DrawTextWithShadow(text, font, pos);
dc.DrawTextWithShadowOrStroke(text, font, pos);
if (nativeLook()) font.color = font_color;
}
+13 -10
View File
@@ -182,27 +182,27 @@ RotatedDC::RotatedDC(DC& dc, const Rotation& rotation, RenderQuality quality)
// ----------------------------------------------------------------------------- : RotatedDC : Drawing
void RotatedDC::DrawText(const String& text, const RealPoint& pos, int blur_radius, int boldness, double stretch_) {
DrawText(text, pos, dc.GetTextForeground(), blur_radius, boldness, stretch_);
void RotatedDC::DrawText(const String& text, const RealPoint& pos, int blur_radius, Color stroke_color, int stroke_radius, double stretch) {
DrawText(text, pos, dc.GetTextForeground(), blur_radius, stroke_color, stroke_radius, stretch);
}
void RotatedDC::DrawText(const String& text, const RealPoint& pos, Color color, int blur_radius, int boldness, double stretch_) {
void RotatedDC::DrawText(const String& text, const RealPoint& pos, Color color, int blur_radius, Color stroke_color, int stroke_radius, double stretch) {
if (text.empty()) return;
if (color.Alpha() == 0) return;
if (quality >= QUALITY_AA) {
RealRect r(pos, GetTextExtent(text));
RealRect r_ext = trRectToBB(r);
RealPoint pos2 = tr(pos);
stretch_ *= getStretch();
if (fabs(stretch_ - 1) > 1e-6) {
r.width *= stretch_;
stretch *= getStretch();
if (fabs(stretch - 1) > 1e-6) {
r.width *= stretch;
RealRect r_ext2 = trRectToBB(r);
pos2.x += r_ext2.x - r_ext.x;
pos2.y += r_ext2.y - r_ext.y;
r_ext.x = r_ext2.x;
r_ext.y = r_ext2.y;
}
draw_resampled_text(dc, pos2, r_ext, stretch_, angle, color, text, blur_radius, boldness);
draw_resampled_text(dc, text, pos2, r_ext, angle, color, blur_radius, stroke_color, stroke_radius, stretch);
} else if (quality >= QUALITY_SUB_PIXEL) {
RealPoint p_ext = tr(pos)*text_scaling;
double usx,usy;
@@ -218,9 +218,12 @@ void RotatedDC::DrawText(const String& text, const RealPoint& pos, Color color,
}
}
void RotatedDC::DrawTextWithShadow(const String& text, const Font& font, const RealPoint& pos, double scale, double stretch) {
DrawText(text, pos + RealSize(font.shadow_displacement_x, font.shadow_displacement_y) * scale, font.shadow_color, font.shadow_blur * scale, 1, stretch);
DrawText(text, pos, font.color, 0, 1, stretch);
void RotatedDC::DrawTextWithShadowOrStroke(const String& text, const Font& font, const RealPoint& pos, double scale, double stretch) {
double s_scale = scale * dc.GetFont().GetPointSize() / text_scaling / 15.;
if (font.hasShadow() && !font.hasStroke()) {
DrawText(text, pos + RealSize(font.shadow_displacement_x, font.shadow_displacement_y) * scale, font.shadow_color, lround(font.shadow_blur * s_scale), Color(0,0,0), 0, stretch);
}
DrawText(text, pos, font.color, lround(font.stroke_blur * s_scale), font.stroke_color, lround(font.stroke_radius * s_scale), stretch);
}
void RotatedDC::DrawBitmap(const Bitmap& bitmap, const RealPoint& pos) {
+4 -4
View File
@@ -157,10 +157,10 @@ public:
// --------------------------------------------------- : Drawing
/// Draw text
void DrawText (const String& text, const RealPoint& pos, int blur_radius = 0, int boldness = 1, double stretch = 1.0);
void DrawText (const String& text, const RealPoint& pos, Color color, int blur_radius = 0, int boldness = 1, double stretch = 1.0);
/// Draw text with the shadow and color settings of the given font
void DrawTextWithShadow(const String& text, const Font& font, const RealPoint& pos, double scale = 1.0, double stretch = 1.0);
void DrawText (const String& text, const RealPoint& pos, int blur_radius = 0, Color stroke_color = Color(0,0,0), int stroke_radius = 0, double stretch = 1.0);
void DrawText (const String& text, const RealPoint& pos, Color color, int blur_radius = 0, Color stroke_color = Color(0,0,0), int stroke_radius = 0, double stretch = 1.0);
/// Draw text with a shadow or stroke, and color settings of the given font
void DrawTextWithShadowOrStroke(const String& text, const Font& font, const RealPoint& pos, double scale = 1.0, double stretch = 1.0);
/// Draw abitmap, it must already be zoomed!
void DrawBitmap(const Bitmap& bitmap, const RealPoint& pos);
/// Draw an image using the given combining mode, the image must already be zoomed!