diff --git a/data/ch-s.mse-locale/locale b/data/ch-s.mse-locale/locale index 7d10745b..c2af934c 100644 --- a/data/ch-s.mse-locale/locale +++ b/data/ch-s.mse-locale/locale @@ -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: 向上移动 diff --git a/data/ch-t.mse-locale/locale b/data/ch-t.mse-locale/locale index 34b6d586..7ce1922e 100644 --- a/data/ch-t.mse-locale/locale +++ b/data/ch-t.mse-locale/locale @@ -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: 向上移動 diff --git a/data/da.mse-locale/locale b/data/da.mse-locale/locale index 85eb9957..429975a3 100644 --- a/data/da.mse-locale/locale +++ b/data/da.mse-locale/locale @@ -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 diff --git a/data/de.mse-locale/locale b/data/de.mse-locale/locale index ef769096..88f6272c 100644 --- a/data/de.mse-locale/locale +++ b/data/de.mse-locale/locale @@ -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' diff --git a/data/en.mse-locale/locale b/data/en.mse-locale/locale index 72ae8b85..72642cf9 100644 --- a/data/en.mse-locale/locale +++ b/data/en.mse-locale/locale @@ -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 diff --git a/data/es.mse-locale/locale b/data/es.mse-locale/locale index 7a1a103c..2cb2f4cd 100644 --- a/data/es.mse-locale/locale +++ b/data/es.mse-locale/locale @@ -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 diff --git a/data/fr.mse-locale/locale b/data/fr.mse-locale/locale index cabbab72..bef59404 100644 --- a/data/fr.mse-locale/locale +++ b/data/fr.mse-locale/locale @@ -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' diff --git a/data/it.mse-locale/locale b/data/it.mse-locale/locale index 6be87372..cdd60a40 100644 --- a/data/it.mse-locale/locale +++ b/data/it.mse-locale/locale @@ -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' diff --git a/data/jp.mse-locale/locale b/data/jp.mse-locale/locale index f41908c9..2b4319a7 100644 --- a/data/jp.mse-locale/locale +++ b/data/jp.mse-locale/locale @@ -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: 上へ移動 diff --git a/data/ko.mse-locale/locale b/data/ko.mse-locale/locale index ed2cb886..15794c8e 100644 --- a/data/ko.mse-locale/locale +++ b/data/ko.mse-locale/locale @@ -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: 숭진시키다 diff --git a/data/pl.mse-locale/locale b/data/pl.mse-locale/locale index 178bc53f..c828fd85 100644 --- a/data/pl.mse-locale/locale +++ b/data/pl.mse-locale/locale @@ -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ę diff --git a/data/pt-br.mse-locale/locale b/data/pt-br.mse-locale/locale index eb03848c..9ddfb363 100644 --- a/data/pt-br.mse-locale/locale +++ b/data/pt-br.mse-locale/locale @@ -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' diff --git a/data/ru.mse-locale/locale b/data/ru.mse-locale/locale index a5caaecd..7e40d141 100644 --- a/data/ru.mse-locale/locale +++ b/data/ru.mse-locale/locale @@ -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: Выше diff --git a/src/data/font.cpp b/src/data/font.cpp index c08cd752..a99ee3de 100644 --- a/src/data/font.cpp +++ b/src/data/font.cpp @@ -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); } diff --git a/src/data/font.hpp b/src/data/font.hpp index b692b980..32d33b68 100644 --- a/src/data/font.hpp +++ b/src/data/font.hpp @@ -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 { public: - Scriptable name; ///< Name of the font + Scriptable name; ///< Name of the referenced font Scriptable italic_name; ///< Font name for italic text (optional) Scriptable size; ///< Size of the font Scriptable 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 to use - Scriptable shadow_color; ///< Color for shadow - Scriptable shadow_displacement_x;///< Position of the shadow - Scriptable shadow_displacement_y;///< Position of the shadow - Scriptable shadow_blur; ///< Blur radius of the shadow + Scriptable shadow_color; ///< Color for the shadow + Scriptable shadow_displacement_x;///< Offset of the shadow in pixels, for a font size of 15 + Scriptable shadow_displacement_y;///< Offset of the shadow in pixels, for a font size of 15 + Scriptable shadow_blur; ///< Blur radius of the shadow in pixels, for a font size of 15 + Scriptable stroke_color; ///< Color for the stroke + Scriptable stroke_radius; ///< Thickness of the stroke in pixels, for a font size of 15 + Scriptable stroke_blur; ///< Blur radius of the stroke in pixels, for a font size of 15 Color separator_color; ///< Color for text int flags; ///< FontFlags for this font @@ -61,16 +64,20 @@ public: bool update(Context& ctx); /// 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 style to a font, and optionally change the font family, color and size + /// 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, 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: diff --git a/src/data/symbol_font.cpp b/src/data/symbol_font.cpp index 84025494..8b9fd9fe 100644 --- a/src/data/symbol_font.cpp +++ b/src/data/symbol_font.cpp @@ -7,6 +7,7 @@ // ----------------------------------------------------------------------------- : Includes #include +#include #include #include #include @@ -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)); - RealRect sym_rect = split_left(rect, size); - drawSymbol(dc, sym_rect, font_size, align, *sym.symbol, sym.draw_text); + 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); + // 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,8 +669,17 @@ 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 { return !!font; @@ -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); } diff --git a/src/data/symbol_font.hpp b/src/data/symbol_font.hpp index 4752cc83..c157f422 100644 --- a/src/data/symbol_font.hpp +++ b/src/data/symbol_font.hpp @@ -19,7 +19,8 @@ DECLARE_POINTER_TYPE(Font); DECLARE_POINTER_TYPE(SymbolFont); DECLARE_POINTER_TYPE(SymbolInFont); DECLARE_POINTER_TYPE(InsertSymbolMenu); -class RotatedDC; +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& 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& 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,15 +159,33 @@ 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 name; ///< Font package name, can be changed with script - Scriptable size; ///< Size of the font - double scale_down_to; ///< Mimumum size of the font - Scriptable alignment; ///< Alignment of symbols in a line of text - SymbolFontP font; ///< The font, if it is loaded + Scriptable name; ///< The referenced symbol font's package name (folder name) + Scriptable size; ///< Size of the font + double scale_down_to; ///< Minimum size of the font + Scriptable underline; ///< Underlined? + Scriptable strikethrough; ///< Struck through? + Scriptable alignment; ///< Alignment of symbols in a line of text + Scriptable shadow_color; ///< Color for the shadow + Scriptable shadow_displacement_x;///< Offset of the shadow in pixels, for a font size of 15 + Scriptable shadow_displacement_y;///< Offset of the shadow in pixels, for a font size of 15 + Scriptable shadow_blur; ///< Blur radius of the shadow in pixels, for a font size of 15 + Scriptable stroke_color; ///< Color for the stroke + Scriptable stroke_radius; ///< Thickness of the stroke in pixels, for a font size of 15 + Scriptable 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(); diff --git a/src/gfx/gfx.hpp b/src/gfx/gfx.hpp index e7008464..fbcdbbb9 100644 --- a/src/gfx/gfx.hpp +++ b/src/gfx/gfx.hpp @@ -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 in, int i, int x, int y, int width, int height); +Byte thicken_pixel_alpha_5x5(std::vector in, int i, int x, int y, int width, int height); +Byte thicken_pixel_alpha_7x7(std::vector 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 diff --git a/src/gfx/image_effects.cpp b/src/gfx/image_effects.cpp index 51a3c980..acd2155f 100644 --- a/src/gfx/image_effects.cpp +++ b/src/gfx/image_effects.cpp @@ -9,6 +9,9 @@ #include #include #include +#if defined(__WXMSW__) && wxUSE_WXDIB +#include +#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 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 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 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 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) { diff --git a/src/gfx/resample_text.cpp b/src/gfx/resample_text.cpp index b0a1064b..8427ae99 100644 --- a/src/gfx/resample_text.cpp +++ b/src/gfx/resample_text.cpp @@ -10,9 +10,6 @@ #include #include #include // clearDC_black -#if defined(__WXMSW__) && wxUSE_WXDIB - #include -#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(rect.width) + 3 + 2 * blur_radius, h = static_cast(rect.height) + 1 + 2 * blur_radius; + int w = static_cast(rect.width) + 4, h = static_cast(rect.height) + 2; // determine sub-pixel position - int xi = static_cast(rect.x) - blur_radius / text_scaling, - yi = static_cast(rect.y) - blur_radius / text_scaling; + int xi = static_cast(rect.x), + yi = static_cast(rect.y); int xsub = static_cast(text_scaling * (pos.x - xi)), ysub = static_cast(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 - if (color.Alpha() != 255) { - set_alpha(img_small, color.Alpha() / 255.); - } - // blur - for (int i = 0 ; i < blur_radius ; ++i) { - blur_image_alpha(img_small); - } - // step 3. draw to dc - for (int i = 0 ; i < repeat ; ++i) { - dc.DrawBitmap(img_small, xi, yi); + 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, color.Alpha() / 255.); + } + 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(s_img, 3); + } + 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); } } diff --git a/src/render/text/font.cpp b/src/render/text/font.cpp index 39243a73..61eebf65 100644 --- a/src/render/text/font.cpp +++ b/src/render/text/font.cpp @@ -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; } diff --git a/src/render/text/symbol.cpp b/src/render/text/symbol.cpp index 411d1985..5f0de5bb 100644 --- a/src/render/text/symbol.cpp +++ b/src/render/text/symbol.cpp @@ -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)); } } diff --git a/src/render/value/choice.cpp b/src/render/value/choice.cpp index 26996ab9..36b2185b 100644 --- a/src/render/value/choice.cpp +++ b/src/render/value/choice.cpp @@ -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; } } diff --git a/src/render/value/image.cpp b/src/render/value/image.cpp index 442d019c..98b5a7a5 100644 --- a/src/render/value/image.cpp +++ b/src/render/value/image.cpp @@ -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; } } diff --git a/src/render/value/multiple_choice.cpp b/src/render/value/multiple_choice.cpp index 193fc464..6f2a8e85 100644 --- a/src/render/value/multiple_choice.cpp +++ b/src/render/value/multiple_choice.cpp @@ -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; } diff --git a/src/render/value/package_choice.cpp b/src/render/value/package_choice.cpp index 529d5bf5..8e87b69f 100644 --- a/src/render/value/package_choice.cpp +++ b/src/render/value/package_choice.cpp @@ -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; } diff --git a/src/util/rotation.cpp b/src/util/rotation.cpp index bbfe96f3..b8e64ea4 100644 --- a/src/util/rotation.cpp +++ b/src/util/rotation.cpp @@ -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; @@ -216,11 +216,14 @@ void RotatedDC::DrawText(const String& text, const RealPoint& pos, Color color, dc.SetTextForeground(color); dc.DrawRotatedText(text, (int) p_ext.x, (int) p_ext.y, rad_to_deg(angle)); } -} +} -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) { diff --git a/src/util/rotation.hpp b/src/util/rotation.hpp index e97e02d1..9a669b20 100644 --- a/src/util/rotation.hpp +++ b/src/util/rotation.hpp @@ -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!