diff --git a/krita/kritamenu.action b/krita/kritamenu.action index e452c96c91..bd4a5b3bc4 100644 --- a/krita/kritamenu.action +++ b/krita/kritamenu.action @@ -1,1741 +1,1742 @@ File document-new &New Create new document New 0 0 Ctrl+N false document-open &Open... Open an existing document Open 0 0 Ctrl+O false document-open-recent Open &Recent Open a document which was recently opened Open Recent 1 0 false document-save &Save Save Save 1 0 Ctrl+S false document-save-as Save &As... Save document under a new name Save As 1 0 Ctrl+Shift+S false document-import Open ex&isting Document as Untitled Document... Open existing Document as Untitled Document Open existing Document as Untitled Document 0 0 false document-export E&xport... Export Export 1 0 false application-pdf &Export as PDF... Export as PDF Export as PDF 1 0 false Import animation frames... Import animation frames Import animation frames 1 0 false &Render Animation... Render Animation to GIF, Image Sequence or Video Render Animation 1000 0 false &Render Image Sequence Again Render Animation to Image Sequence Again Render Animation 1000 0 false Save Incremental &Version Save Incremental Version Save Incremental Version 1 0 Ctrl+Alt+S false Save Incremental &Backup Save Incremental Backup Save Incremental Backup 1 0 F4 false &Create Template From Image... Create Template From Image Create Template From Image 1 0 false Create Copy &From Current Image Create Copy From Current Image Create Copy From Current Image 1 0 false document-print &Print... Print document Print 1 0 Ctrl+P false document-print-preview Print Previe&w Show a print preview of document Print Preview 1 0 false configure &Document Information Document Information Document Information 1 0 false &Close All Close All Close All 1 0 Ctrl+Shift+W false C&lose Close Close 1 0 false &Quit Quit application Quit 0 0 Ctrl+Q false Edit edit-undo Undo Undo last action Undo 1 0 Ctrl+Z false edit-redo Redo Redo last undone action Redo 1 0 Ctrl+Shift+Z false edit-cut Cu&t Cut selection to clipboard Cut 0 0 Ctrl+X false edit-copy &Copy Copy selection to clipboard Copy 0 0 Ctrl+C false C&opy (sharp) Copy (sharp) Copy (sharp) 100000000 0 false Cut (&sharp) Cut (sharp) Cut (sharp) 100000000 0 false Copy &merged Copy merged Copy merged 100000000 0 Ctrl+Shift+C false edit-paste &Paste Paste clipboard content Paste 0 0 Ctrl+V false Paste at Cursor Paste at cursor Paste at cursor 0 0 Ctrl+Alt+V false Paste into &New Image Paste into New Image Paste into New Image 0 0 Ctrl+Shift+N false edit-clear C&lear Clear Clear 1 0 Del false &Fill with Foreground Color Fill with Foreground Color Fill with Foreground Color 10000 1 Shift+Backspace false Fill &with Background Color Fill with Background Color Fill with Background Color 10000 1 Backspace false F&ill with Pattern Fill with Pattern Fill with Pattern 10000 1 false Stro&ke selected shapes Stroke selected shapes Stroke selected shapes 1000000000 0 false Stroke Selec&tion... Stroke selection Stroke selection 10000000000 0 false Delete keyframe Delete keyframe Delete keyframe 100000 0 false Window window-new &New Window New Window New Window 0 0 false N&ext Next Next 10 0 false Previous Previous Previous false View &Show Canvas Only Show just the canvas or the whole window Show Canvas Only 0 0 Tab true view-fullscreen F&ull Screen Mode Display the window in full screen Full Screen Mode 0 0 Ctrl+Shift+F true &Wrap Around Mode Wrap Around Mode Wrap Around Mode 1 0 W true &Instant Preview Mode Instant Preview Mode Instant Preview Mode 1 0 Shift+L true Soft Proofing Turns on Soft Proofing Turns on Soft Proofing Ctrl+Y true Out of Gamut Warnings Turns on warnings for colors out of proofed gamut, needs soft proofing to be turned on. Turns on warnings for colors out of proofed gamut, needs soft proofing to be turned on. Ctrl+Shift+Y true mirror-view Mirror View Mirror View Mirror View M false zoom-original &Reset zoom Reset zoom Reset zoom 1 0 Ctrl+0 false zoom-in Zoom &In Zoom In 0 0 Ctrl++ false zoom-out Zoom &Out Zoom Out 0 0 Ctrl+- false rotate-canvas-right Rotate &Canvas Right Rotate Canvas Right Rotate Canvas Right 1 0 Ctrl+] false rotate-canvas-left Rotate Canvas &Left Rotate Canvas Left Rotate Canvas Left 1 0 Ctrl+[ false rotation-reset Reset Canvas Rotation Reset Canvas Rotation Reset Canvas Rotation 1 0 false Show &Rulers The rulers show the horizontal and vertical positions of the mouse on the image and can be used to position your mouse at the right place on the canvas. <p>Uncheck this to hide the rulers.</p> Show Rulers Show Rulers 1 0 true Rulers Track Pointer The rulers will track current mouse position and show it on screen. It can cause suptle performance slowdown Rulers Track Pointer Rulers Track Pointer 1 0 true Show Guides Show or hide guides Show Guides 1 0 true Lock Guides Lock or unlock guides Lock Guides 1 0 true Snap to Guides Snap cursor to guides position Snap to Guides 1 0 true Show Status &Bar Show or hide the status bar Show Status Bar 0 0 true view-grid Show &Grid Show Grid Show Grid 1000 0 Ctrl+Shift+' true Snap To Grid Snap To Grid Snap To Grid 1000 Ctrl+Shift+; true Show Snap Options Popup Show Snap Options Popup Show Snap Options Popup 1000 Shift+s false Snap Orthogonal Snap Orthogonal Snap Orthogonal 1000 true Snap Node Snap Node Snap Node 1000 true Snap Extension Snap Extension Snap Extension 1000 true Snap Intersection Snap Intersection Snap Intersection 1000 true Snap Bounding Box Snap Bounding Box Snap Bounding Box 1000 true Snap Image Bounds Snap Image Bounds Snap Image Bounds 1000 true Snap Image Center Snap Image Center Snap Image Center 1000 true S&how Painting Assistants Show Painting Assistants Show Painting Assistants 1000 0 true Show &Assistant Previews Show Assistant Previews Show Assistant Previews 1000 0 true Image document-properties &Properties... Properties Properties 1000 0 false format-stroke-color &Image Background Color and Transparency... Change the background color of the image Image Background Color and Transparency 1000 0 false &Convert Image Color Space... Convert Image Color Space Convert Image Color Space 1000 0 false trim-to-image &Trim to Image Size Trim to Image Size Trim to Image Size 1 0 false Trim to Current &Layer Trim to Current Layer Trim to Current Layer 100000 0 false Trim to S&election Trim to Selection Trim to Selection 100000000 0 false &Rotate Image... Rotate Image Rotate Image 1000 0 false object-rotate-right Rotate &Image 90° to the Right Rotate Image 90° to the Right Rotate Image 90° to the Right 1000 0 false object-rotate-left Rotate Image &90° to the Left Rotate Image 90° to the Left Rotate Image 90° to the Left 1000 0 false Rotate Image &180° Rotate Image 180° Rotate Image 180° 1000 0 false &Shear Image... Shear Image Shear Image 1000 0 false symmetry-horizontal &Mirror Image Horizontally Mirror Image Horizontally Mirror Image Horizontally 1000 0 false symmetry-vertical Mirror Image &Vertically Mirror Image Vertically Mirror Image Vertically 1000 0 false Scale Image To &New Size... Scale Image To New Size Scale Image To New Size 1000 0 Ctrl+Alt+I false &Offset Image... Offset Image Offset Image 1000 0 false R&esize Canvas... Resize Canvas Resize Canvas 1000 0 Ctrl+Alt+C false Im&age Split Image Split Image Split 1000 0 false Separate Ima&ge... Separate Image Separate Image 1000 0 false Select edit-select-all Select &All Select All Select All 0 0 Ctrl+A false edit-select-all &Deselect Deselect Deselect 1100000000 0 Ctrl+Shift+A false &Reselect Reselect Reselect 0 0 Ctrl+Shift+D false &Invert Invert Invert 10000 0 Ctrl+I false &Convert to Vector Selection Convert to Vector Selection Convert to Vector Selection 10000000000 0 false Convert Shapes to &Vector Selection Convert Shapes to Vector Selection Convert Shapes to Vector Selection 1000000000 0 false &Feather Selection... Feather Selection Feather Selection 10000000000 100 Shift+F6 false Dis&play Selection Display Selection Display Selection 1000 0 Ctrl+H true Sca&le... Scale Scale 100000000 100 false S&elect from Color Range... Select from Color Range Select from Color Range 10000 100 false Select &Opaque Select Opaque Select Opaque 10000 100 false &Grow Selection... Grow Selection Grow Selection 10000000000 100 false S&hrink Selection... Shrink Selection Shrink Selection 10000000000 100 false &Border Selection... Border Selection Border Selection 10000000000 100 false S&mooth Smooth Smooth 10000000000 100 false Filter &Apply Filter Again Apply Filter Again Apply Filter Again 0 0 Ctrl+F false Adjust Adjust Adjust false Artistic Artistic Artistic false Blur Blur Blur false Colors Colors Colors false Edge Detection Edge Detection Edge Detection false Enhance Enhance Enhance false Emboss Emboss Emboss false Map Map Map false Other Other Other false gmic Start G'MIC-Qt Start G'Mic-Qt Start G'Mic-Qt false gmic Re-apply the last G'MIC filter Apply the last G'Mic-Qt action again Apply the last G'Mic-Qt action again false + Tools media-record &Start recording macro Start recording macro Start recording macro 1000 0 false media-playback-stop Stop &recording actions Stop recording actions Stop recording actions 1000 0 false media-playback-start &Open and play... Open and play Open and play 0 0 false document-edit Open &and edit... Open and edit Open and edit 0 0 false Settings configure &Configure Krita... Configure Krita Configure Krita 0 0 false &Manage Resources... Manage Resources Manage Resources 0 0 false preferences-desktop-locale Switch Application &Language... Switch Application Language Switch Application Language false &Show Dockers Show Dockers Show Dockers 0 0 true Sho&w Docker Titlebars Show Docker Titlebars Show Docker Titlebars 0 0 true configure Configure Tool&bars... Configure Toolbars Configure Toolbars 0 0 false Dockers Dockers Dockers false &Themes Themes Themes false im-user Active Author Profile Active Author Profile Active Author Profile configure-shortcuts Configure S&hortcuts... Configure Shortcuts Configure Shortcuts 0 0 false &Window Window Window false Help help-contents Krita &Handbook Krita Handbook Krita Handbook F1 false tools-report-bug &Report Bug... Report Bug Report Bug false calligrakrita &About Krita About Krita About Krita false kde About &KDE About KDE About KDE false Brushes and Stuff &Gradients Gradients Gradients false &Patterns Patterns Patterns false &Color Color Color false &Painter's Tools Painter's Tools Painter's Tools false Brush composite Brush composite Brush composite false Brush option slider 1 Brush option slider 1 Brush option slider 1 false Brush option slider 2 Brush option slider 2 Brush option slider 2 false Brush option slider 3 Brush option slider 3 Brush option slider 3 false Mirror Mirror Mirror false Workspaces Workspaces Workspaces false diff --git a/krita/org.kde.krita.appdata.xml b/krita/org.kde.krita.appdata.xml index 3eb2dc9814..80ef14001c 100644 --- a/krita/org.kde.krita.appdata.xml +++ b/krita/org.kde.krita.appdata.xml @@ -1,166 +1,167 @@ org.kde.krita.desktop CC0-1.0 Krita + Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita xxKritaxx Digital Painting, Creative Freedom Pintura dixital, llibertá creativa Digitalno crtanje, kreativna sloboda Dibuix digital, Llibertat creativa Dibuix digital, Llibertat creativa Digitální malování, svoboda tvorby Digital tegning, kunstnerisk frihed Digitales Malen, kreative Freiheit Digital Painting, Creative Freedom Pintura digital, libertad creativa Digitaalne joonistamine, loominguline vabadus Digitaalimaalaus, luova vapaus Peinture numérique, liberté créatrice Debuxo dixital, liberdade creativa Pictura digital, Libertate creative Pittura digitale, libertà creativa Digital Painting, Creative Freedom Cyfrowe malowanie, Wolność Twórcza Pintura Digital, Liberdade Criativa Pintura Digital, Liberdade Criativa Цифровое рисование. Творческая свобода Digitálne maľovanie, kreatívna sloboda Digital målning, kreativ frihet Цифрове малювання, творча свобода xxDigital Painting, Creative Freedomxx 数码绘图,自由创作

Krita is the full-featured digital art studio.

Krita ye l'estudiu completu d'arte dixital.

Krita je potpuni digitalni umjetnički studio.

Krita és l'estudi d'art digital ple de funcionalitats.

Krita és l'estudi d'art digital ple de funcionalitats.

Krita ist ein digitales Designstudio mit umfangreichen Funktionen.

Krita is the full-featured digital art studio.

Krita es un estudio de arte digital completo

Krita on rohkete võimalustega digitaalkunstistuudio.

Krita on täyspiirteinen digitaiteen ateljee.

Krita est le studio d'art numérique complet.

Krita é un estudio completo de arte dixital.

Krita es le studio de arte digital complete.

Krita è uno studio d'arte digitale completo.

Krita は、フル機能を備えたデジタルなアートスタジオです。

Krita is de digitale kunststudio vol mogelijkheden.

Krita jest pełnowymiarowym, cyfrowym studiem artystycznym

O Krita é o estúdio de arte digital completo.

O Krita é o estúdio de arte digital completo.

Krita полнофункциональный инструмент для создания цифровой графики.

Krita je plne vybavené digitálne umelecké štúdio.

Krita är den fullfjädrade digitala konststudion.

Krita — повноцінний комплекс для створення цифрових художніх творів.

xxKrita is the full-featured digital art studio.xx

Krita 是全功能的数码艺术工作室。

It is perfect for sketching and painting, and presents an end–to–end solution for creating digital painting files from scratch by masters.

On je savršen za skiciranje i slikanje i predstavlja finalno rješenje za kreiranje digitalnih slika od nule s majstorima

És perfecte per fer esbossos i pintar, i presenta una solució final per crear fitxers de dibuix digital des de zero per a mestres.

És perfecte per fer esbossos i pintar, i presenta una solució final per crear fitxers de dibuix digital des de zero per a mestres.

It is perfect for sketching and painting, and presents an end–to–end solution for creating digital painting files from scratch by masters.

Es perfecto para diseñar y pintar, y ofrece una solución completa para crear desde cero archivos de pintura digital apta para profesionales.

See on suurepärane töövahend visandite ja joonistuste valmistamiseks ning annab andekatele kunstnikele võimaluse luua digitaalpilt algusest lõpuni just oma käe järgi.

Se on täydellinen luonnosteluun ja maalaukseen ja tarjoaa kokonaisratkaisun digitaalisten kuvatiedostojen luomiseen alusta alkaen.

Il est parfait pour crayonner et peindre, et constitue une solution de bout en bout pour créer des fichier de peinture numérique depuis la feuille blanche jusqu'au épreuves finales.

Resulta perfecto para debuxar e pintar, e presenta unha solución completa que permite aos mestres crear ficheiros de debuxo dixital desde cero.

Illo es perfecte pro schizzar e pinger, e presenta un solution ab fin al fin pro crear files de pictura digital ab grattamentos per maestros.

Perfetto per fare schizzi e dipingere, prevede una soluzione completa che consente agli artisti di creare file di dipinti digitali partendo da zero.

Het is perfect voor schetsen en schilderen en zet een end–to–end oplossing voor het maken van digitale bestanden voor schilderingen vanuit het niets door meesters.

Nadaje się perfekcyjnie do szkicowania i malowania i dostarcza zupełnego rozwiązania dla tworzenia plików malowideł cyfrowych od zalążka.

É perfeito para desenhos e pinturas, oferecendo uma solução final para criar ficheiros de pintura digital do zero por mestres.

É perfeito para desenhos e pinturas, oferecendo uma solução final para criar arquivos de desenho digital feitos a partir do zero por mestres.

Она превосходно подходит для набросков и рисования, предоставляя мастерам самодостаточный инструмент для создания цифровой живописи с нуля.

Je ideálna na skicovanie a maľovanie a poskytuje end-to-end riešenie na vytváranie súborov digitálneho maľovania od základu od profesionálov.

Den är perfekt för att skissa och måla, samt erbjuder en helomfattande lösning för att skapa digitala målningsfiler från grunden av mästare.

Цей комплекс чудово пасує для створення ескізів та художніх зображень і є самодостатнім набором для створення файлів цифрових полотен «з нуля» для справжніх художників.

xxIt is perfect for sketching and painting, and presents an end–to–end solution for creating digital painting files from scratch by masters.xx

适合做草图和绘画,为艺术大师提供了从草稿到数码绘画的完整解决方案。

Krita is a great choice for creating concept art, comics, textures for rendering and matte paintings. Krita supports many colorspaces like RGB and CMYK at 8 and 16 bits integer channels, as well as 16 and 32 bits floating point channels.

Krita je odličan izbor za kreiranje konceptualne umjetnosti, stripove, teksture za obradu i mat slike. Krita podržava mnoge prostore boja kao RGB i CMIK na 8 i 16 bitnim cjelobrojnim kanalimaa, kao i 16 i 32 bita floating point kanalima.

El Krita és una gran elecció per crear art conceptual, còmics, textures per renderitzar i pintures «matte». El Krita permet molts espais de color com el RGB i el CMYK a 8 i 16 bits de canals sencers, així com 16 i 32 bits de canals de coma flotant.

El Krita és una gran elecció per crear art conceptual, còmics, textures per renderitzar i pintures «matte». El Krita permet molts espais de color com el RGB i el CMYK a 8 i 16 bits de canals sencers, així com 16 i 32 bits de canals de coma flotant.

Krita is a great choice for creating concept art, comics, textures for rendering and matte paintings. Krita supports many colourspaces like RGB and CMYK at 8 and 16 bits integer channels, as well as 16 and 32 bits floating point channels.

Krita es una gran elección para crear arte conceptual, cómics, texturas para renderizar y «matte paintings». Krita permite el uso de muchos espacios de color, como, por ejemplo, RGB y CMYK, tanto en canales de enteros de 8 y 16 bits, así como en canales de coma flotante de 16 y 32 bits.

Krita on üks paremaid valikuid kontseptuaalkunsti, koomiksite, tekstuuride ja digitaalmaalide loomiseks. Krita toetab paljusid värviruume, näiteks RGB ja CMYK 8 ja 16 täisarvulise bitiga kanali kohta, samuti 16 ja 32 ujukomabitiga kanali kohta.

Krita on hyvä valinta konseptikuvituksen, sarjakuvien, pintakuvioiden ja maalausten luomiseen. Krita tukee useita väriavaruuksia kuten RGB:tä ja CMYK:ta 8 ja 16 bitin kokonaisluku- samoin kuin 16 ja 32 bitin liukulukukanavin.

Krita est un très bon choix pour créer des concepts arts, des bandes-dessinées, des textures de rendu et des peintures. Krita prend en charge plusieurs espaces de couleurs comme RVB et CMJN avec les canaux de 8 et 16 bits entiers ainsi que les canaux de 16 et 32 bits flottants.

Krita é unha gran opción para crear arte conceptual, texturas para renderización e pinturas mate. Krita permite usar moitos espazos de cores como RGB e CMYK con canles de 8 e 16 bits, así como canles de coma flotante de 16 e 32 bits.

Krita es un grande selection pro crear arte de concepto, comics, texturas pro rendering e picturas opac. Krita supporta multe spatios de colores como RGB e CMYK con canales de integer a 8 e 16 bits, como anque canales floating point a 16 e 32 bits.

Krita rappresenta una scelta ottimale per la creazione di arte concettuale, fumetti e texture per il rendering e il matte painting. Krita supporta molti spazi colori come RGB e CMYK a 8 e 16 bit per canali interi e 16 e 32 bit per canali a virgola mobile.

コンセプトアート、コミック、3DCG 用テクスチャ、マットペイントを制作する方にとって、Krita は最適な選択です。Krita は、8/16 ビット整数/チャンネル、および 16/32 ビット浮動小数点/チャンネルの RGB や CMYK をはじめ、さまざまな色空間をサポートしています。

Krita is een goede keuze voor het maken van kunstconcepten, strips, textuur voor weergeven en matte schilderijen. Krita ondersteunt vele kleurruimten zoals RGB en CMYK in 8 en 16 bits kanalen met gehele getallen, evenals 16 en 32 bits kanalen met drijvende komma.

Krita jest świetnym wyborem przy tworzeniu koncepcyjnej sztuki, komiksów, tekstur do wyświetlania i kaszet. Krita obsługuje wiele przestrzeni barw takich jak RGB oraz CMYK dla kanałów 8 oraz 16 bitowych wyrażonych w l. całkowitych, a także 16 oraz 32 bitowych wyrażonych w l. zmiennoprzecinkowych.

O Krita é uma óptima escolha para criar arte conceptual, banda desenhada, texturas para desenho e pinturas. O Krita suporta diversos espaços de cores como o RGB e o CMYK com canais de cores inteiros a 8 e 16 bits, assim como canais de vírgula flutuante a 16 e a 32 bits.

O Krita é uma ótima escolha para criação de arte conceitual, histórias em quadrinhos, texturas para desenhos e pinturas. O Krita tem suporte a diversos espaços de cores como RGB e CMYK com canais de cores inteiros de 8 e 16 bits, assim como canais de ponto flutuante de 16 e 32 bits.

Krita - отличный выбор для создания концепт-артов, комиксов, текстур для рендеринга и рисования. Она поддерживает множество цветовых пространств включая RGB и CMYK с 8 и 16 целыми битами на канал, а также 16 и 32 битами с плавающей запятой на канал.

Krita je výborná voľba pre vytváranie konceptového umenia, textúr na renderovanie a matné kresby. Krita podporuje mnoho farebných priestorov ako RGB a CMYK na 8 a 16 bitových celočíselných kanáloch ako aj 16 a 32 bitových reálnych kanáloch.

Krita är ett utmärkt val för att skapa concept art, serier, strukturer för återgivning och bakgrundsmålningar. Krita stöder många färgrymder som RGB och CMYK med 8- och 16-bitars heltal, samt 16- och 32-bitars flyttal.

Krita — чудовий інструмент для створення концептуального живопису, коміксів, текстур для моделей та декорацій. У Krita передбачено підтримку багатьох просторів кольорів, зокрема RGB та CMYK з 8-бітовими та 16-бітовими цілими значеннями, а також 16-бітовими та 32-бітовими значеннями з рухомою крапкою для каналів кольорів.

xxKrita is a great choice for creating concept art, comics, textures for rendering and matte paintings. Krita supports many colorspaces like RGB and CMYK at 8 and 16 bits integer channels, as well as 16 and 32 bits floating point channels.xx

Krita 是创建抽象艺术,漫画,渲染纹理和亚光绘画的理想选择。Krita 支持非常多的色彩空间,比如 8 位和 16 位整数通道以及 16 位和 32 位浮点通道的 RGB 和 CMYK。

Have fun painting with the advanced brush engines, amazing filters and many handy features that make Krita enormously productive.

Zabavite se kreirajući napredne pogone četki, filtere i mnoge praktične osobine koje čine Krita vrlo produktivnim.

Gaudiu pintant amb els motors de pinzells avançats, els filtres impressionants i moltes funcionalitats útils que fan el Krita molt productiu.

Gaudiu pintant amb els motors de pinzells avançats, els filtres impressionants i moltes funcionalitats útils que fan el Krita molt productiu.

Have fun painting with the advanced brush engines, amazing filters and many handy features that make Krita enormously productive.

Diviértase pintando con los avanzados motores de pinceles, los espectaculares filtros y muchas funcionalidades prácticas que hacen que Krita sea enormemente productivo.

Joonistamise muudavad tunduvalt lõbusamaks võimsad pintslimootorid, imetabased filtrid ja veel paljud käepärased võimalused, mis muudavad Krita kasutaja tohutult tootlikuks.

Pidä hauskaa maalatessasi edistyneillä sivellinmoottoreilla, hämmästyttävillä suotimilla ja monilla muilla kätevillä ominaisuuksilla, jotka tekevät Kritasta tavattoman tehokkaan.

Amusez-vous à peindre avec les outils de brosse avancés, les filtres incroyables et les nombreuses fonctionnalités pratiques qui rendent Krita extrêmement productif.

Goza debuxando con motores de pincel avanzados, filtros fantásticos e moitas outras funcionalidades útiles que fan de Krita un programa extremadamente produtivo.

Amusa te a pinger con le motores de pincel avantiate, filtros stupende e multe characteristicas amical que face Krita enormemente productive.

Divertiti a dipingere con gli avanzati sistemi di pennelli, i sorprendenti filtri e molte altre utili caratteristiche che fanno di Krita un software enormemente produttivo.

Krita のソフトウェアとしての生産性を高めている先進的なブラシエンジンや素晴らしいフィルタのほか、便利な機能の数々をお楽しみください。

Veel plezier met schilderen met the geavanceerde penseel-engines, filters vol verbazing en vele handige mogelijkheden die maken dat Krita enorm productief is.

Baw się przy malowaniu przy użyciu zaawansowanych silników pędzli, zadziwiających filtrów i wielu innych przydatnych cech, które czynią z Krity bardzo produktywną.

Divirta-se a pintar com os motores de pincéis avançados, os filtros espantosos e muitas outras funcionalidades úteis que tornam o Krita altamente produtivo.

Divirta-se pintando com os mecanismos de pincéis avançados, filtros maravilhosos e muitas outras funcionalidades úteis que tornam o Krita altamente produtivo.

Получайте удовольствие от использования особых кистевых движков, впечатляющих фильтров и множества других функций, делающих Krita сверхпродуктивной.

Užívajte si maľovanie s pokročilými kresliacimi enginmi, úžasnými filtrami a mnohými užitočnými funkciami, ktoré robia Kritu veľmi produktívnu.

Ha det så kul vid målning med de avancerade penselfunktionerna, fantastiska filtren och många praktiska funktioner som gör Krita så enormt produktiv.

Отримуйте задоволення від малювання за допомогою пензлів з найширшими можливостями, чудових фільтрів та багатьох зручних можливостей, які роблять Krita надзвичайно продуктивним засобом малювання.

xxHave fun painting with the advanced brush engines, amazing filters and many handy features that make Krita enormously productive.xx

尽情使用高级笔刷引擎,超赞的滤镜和很多手绘特性,发挥 Krita 绝佳的创造力。

https://www.krita.org/ https://krita.org/about/faq/ https://krita.org/support-us/donations/ https://docs.krita.org/Category:Tutorials http://files.kde.org/krita/marketing/appdata/2016-05-24_screenshot_002.png http://files.kde.org/krita/marketing/appdata/2016-05-24_screenshot_003.png http://files.kde.org/krita/marketing/appdata/2016-05-24_screenshot_004.png http://files.kde.org/krita/marketing/appdata/2016-05-24_screenshot_005.png foundation@krita.org KDE krita
diff --git a/libs/image/brushengine/kis_paint_information.cc b/libs/image/brushengine/kis_paint_information.cc index 4e066320b6..2c08688b3f 100644 --- a/libs/image/brushengine/kis_paint_information.cc +++ b/libs/image/brushengine/kis_paint_information.cc @@ -1,589 +1,590 @@ /* * Copyright (c) 2007,2010 Cyrille Berger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include #include "kis_paintop.h" #include "kis_algebra_2d.h" #include "kis_lod_transform.h" +#include "kis_spacing_information.h" #include struct KisPaintInformation::Private { EIGEN_MAKE_ALIGNED_OPERATOR_NEW Private(const QPointF & pos_, qreal pressure_, qreal xTilt_, qreal yTilt_, qreal rotation_, qreal tangentialPressure_, qreal perspective_, qreal time_, qreal speed_, bool isHoveringMode_) : pos(pos_), pressure(pressure_), xTilt(xTilt_), yTilt(yTilt_), rotation(rotation_), tangentialPressure(tangentialPressure_), perspective(perspective_), time(time_), speed(speed_), isHoveringMode(isHoveringMode_), randomSource(0), currentDistanceInfo(0), levelOfDetail(0) { } ~Private() { KIS_ASSERT_RECOVER_NOOP(!currentDistanceInfo); } Private(const Private &rhs) { copy(rhs); } Private& operator=(const Private &rhs) { copy(rhs); return *this; } void copy(const Private &rhs) { pos = rhs.pos; pressure = rhs.pressure; xTilt = rhs.xTilt; yTilt = rhs.yTilt; rotation = rhs.rotation; tangentialPressure = rhs.tangentialPressure; perspective = rhs.perspective; time = rhs.time; speed = rhs.speed; isHoveringMode = rhs.isHoveringMode; randomSource = rhs.randomSource; currentDistanceInfo = rhs.currentDistanceInfo; canvasRotation = rhs.canvasRotation; canvasMirroredH = rhs.canvasMirroredH; if (rhs.drawingAngleOverride) { drawingAngleOverride.reset(new qreal(*rhs.drawingAngleOverride)); } levelOfDetail = rhs.levelOfDetail; } QPointF pos; qreal pressure; qreal xTilt; qreal yTilt; qreal rotation; qreal tangentialPressure; qreal perspective; qreal time; qreal speed; bool isHoveringMode; KisRandomSourceSP randomSource; int canvasRotation; bool canvasMirroredH; QScopedPointer drawingAngleOverride; KisDistanceInformation *currentDistanceInfo; int levelOfDetail; void registerDistanceInfo(KisDistanceInformation *di) { currentDistanceInfo = di; } void unregisterDistanceInfo() { currentDistanceInfo = 0; } }; KisPaintInformation::DistanceInformationRegistrar:: DistanceInformationRegistrar(KisPaintInformation *_p, KisDistanceInformation *distanceInfo) : p(_p) { p->d->registerDistanceInfo(distanceInfo); } KisPaintInformation::DistanceInformationRegistrar:: ~DistanceInformationRegistrar() { p->d->unregisterDistanceInfo(); } KisPaintInformation::KisPaintInformation(const QPointF & pos, qreal pressure, qreal xTilt, qreal yTilt, qreal rotation, qreal tangentialPressure, qreal perspective, qreal time, qreal speed) : d(new Private(pos, pressure, xTilt, yTilt, rotation, tangentialPressure, perspective, time, speed, false)) { } KisPaintInformation::KisPaintInformation(const QPointF & pos, qreal pressure, qreal xTilt, qreal yTilt, qreal rotation) : d(new Private(pos, pressure, xTilt, yTilt, rotation, 0.0, 1.0, 0.0, 0.0, false)) { } KisPaintInformation::KisPaintInformation(const QPointF &pos, qreal pressure) : d(new Private(pos, pressure, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, false)) { } KisPaintInformation::KisPaintInformation(const KisPaintInformation& rhs) : d(new Private(*rhs.d)) { } void KisPaintInformation::operator=(const KisPaintInformation & rhs) { *d = *rhs.d; } KisPaintInformation::~KisPaintInformation() { delete d; } bool KisPaintInformation::isHoveringMode() const { return d->isHoveringMode; } KisPaintInformation KisPaintInformation::createHoveringModeInfo(const QPointF &pos, qreal pressure, qreal xTilt, qreal yTilt, qreal rotation, qreal tangentialPressure, qreal perspective, qreal speed, int canvasrotation, bool canvasMirroredH) { KisPaintInformation info(pos, pressure, xTilt, yTilt, rotation, tangentialPressure, perspective, 0, speed); info.d->isHoveringMode = true; info.d->canvasRotation = canvasrotation; info.d->canvasMirroredH = canvasMirroredH; return info; } int KisPaintInformation::canvasRotation() const { return d->canvasRotation; } void KisPaintInformation::setCanvasRotation(int rotation) { if (rotation < 0) { d->canvasRotation= 360- abs(rotation % 360); } else { d->canvasRotation= rotation % 360; } } bool KisPaintInformation::canvasMirroredH() const { return d->canvasMirroredH; } void KisPaintInformation::setCanvasHorizontalMirrorState(bool mir) { d->canvasMirroredH = mir; } void KisPaintInformation::toXML(QDomDocument&, QDomElement& e) const { // hovering mode infos are not supposed to be saved KIS_ASSERT_RECOVER_NOOP(!d->isHoveringMode); e.setAttribute("pointX", QString::number(pos().x(), 'g', 15)); e.setAttribute("pointY", QString::number(pos().y(), 'g', 15)); e.setAttribute("pressure", QString::number(pressure(), 'g', 15)); e.setAttribute("xTilt", QString::number(xTilt(), 'g', 15)); e.setAttribute("yTilt", QString::number(yTilt(), 'g', 15)); e.setAttribute("rotation", QString::number(rotation(), 'g', 15)); e.setAttribute("tangentialPressure", QString::number(tangentialPressure(), 'g', 15)); e.setAttribute("perspective", QString::number(perspective(), 'g', 15)); e.setAttribute("time", QString::number(d->time, 'g', 15)); e.setAttribute("speed", QString::number(d->speed, 'g', 15)); } KisPaintInformation KisPaintInformation::fromXML(const QDomElement& e) { qreal pointX = qreal(KisDomUtils::toDouble(e.attribute("pointX", "0.0"))); qreal pointY = qreal(KisDomUtils::toDouble(e.attribute("pointY", "0.0"))); qreal pressure = qreal(KisDomUtils::toDouble(e.attribute("pressure", "0.0"))); qreal rotation = qreal(KisDomUtils::toDouble(e.attribute("rotation", "0.0"))); qreal tangentialPressure = qreal(KisDomUtils::toDouble(e.attribute("tangentialPressure", "0.0"))); qreal perspective = qreal(KisDomUtils::toDouble(e.attribute("perspective", "0.0"))); qreal xTilt = qreal(KisDomUtils::toDouble(e.attribute("xTilt", "0.0"))); qreal yTilt = qreal(KisDomUtils::toDouble(e.attribute("yTilt", "0.0"))); qreal time = KisDomUtils::toDouble(e.attribute("time", "0")); qreal speed = KisDomUtils::toDouble(e.attribute("speed", "0")); return KisPaintInformation(QPointF(pointX, pointY), pressure, xTilt, yTilt, rotation, tangentialPressure, perspective, time, speed); } const QPointF& KisPaintInformation::pos() const { return d->pos; } void KisPaintInformation::setPos(const QPointF& p) { d->pos = p; } qreal KisPaintInformation::pressure() const { return d->pressure; } void KisPaintInformation::setPressure(qreal p) { d->pressure = p; } qreal KisPaintInformation::xTilt() const { return d->xTilt; } qreal KisPaintInformation::yTilt() const { return d->yTilt; } void KisPaintInformation::overrideDrawingAngle(qreal angle) { d->drawingAngleOverride.reset(new qreal(angle)); } qreal KisPaintInformation::drawingAngleSafe(const KisDistanceInformation &distance) const { if (d->drawingAngleOverride) return *d->drawingAngleOverride; if (!distance.hasLastDabInformation()) { warnKrita << "KisPaintInformation::drawingAngleSafe()" << "Cannot access Distance Info last dab data"; return 0.0; } return distance.nextDrawingAngle(pos()); } KisPaintInformation::DistanceInformationRegistrar KisPaintInformation::registerDistanceInformation(KisDistanceInformation *distance) { return DistanceInformationRegistrar(this, distance); } qreal KisPaintInformation::drawingAngle() const { if (d->drawingAngleOverride) return *d->drawingAngleOverride; if (!d->currentDistanceInfo || !d->currentDistanceInfo->hasLastDabInformation()) { warnKrita << "KisPaintInformation::drawingAngle()" << "Cannot access Distance Info last dab data"; return 0.0; } return d->currentDistanceInfo->nextDrawingAngle(pos()); } void KisPaintInformation::lockCurrentDrawingAngle(qreal alpha_unused) const { Q_UNUSED(alpha_unused); if (!d->currentDistanceInfo || !d->currentDistanceInfo->hasLastDabInformation()) { warnKrita << "KisPaintInformation::lockCurrentDrawingAngle()" << "Cannot access Distance Info last dab data"; return; } qreal angle = d->currentDistanceInfo->nextDrawingAngle(pos(), false); qreal newAngle = angle; if (d->currentDistanceInfo->hasLockedDrawingAngle()) { const qreal stabilizingCoeff = 20.0; const qreal dist = stabilizingCoeff * d->currentDistanceInfo->currentSpacing().scalarApprox(); const qreal alpha = qMax(0.0, dist - d->currentDistanceInfo->scalarDistanceApprox()) / dist; const qreal oldAngle = d->currentDistanceInfo->lockedDrawingAngle(); if (shortestAngularDistance(oldAngle, newAngle) < M_PI / 6) { newAngle = (1.0 - alpha) * oldAngle + alpha * newAngle; } else { newAngle = oldAngle; } } d->currentDistanceInfo->setLockedDrawingAngle(newAngle); } QPointF KisPaintInformation::drawingDirectionVector() const { if (d->drawingAngleOverride) { qreal angle = *d->drawingAngleOverride; return QPointF(cos(angle), sin(angle)); } if (!d->currentDistanceInfo || !d->currentDistanceInfo->hasLastDabInformation()) { warnKrita << "KisPaintInformation::drawingDirectionVector()" << "Cannot access Distance Info last dab data"; return QPointF(1.0, 0.0); } return d->currentDistanceInfo->nextDrawingDirectionVector(pos()); } qreal KisPaintInformation::drawingDistance() const { if (!d->currentDistanceInfo || !d->currentDistanceInfo->hasLastDabInformation()) { warnKrita << "KisPaintInformation::drawingDistance()" << "Cannot access Distance Info last dab data"; return 1.0; } QVector2D diff(pos() - d->currentDistanceInfo->lastPosition()); qreal length = diff.length(); if (d->levelOfDetail) { length *= KisLodTransform::lodToInvScale(d->levelOfDetail); } return length; } qreal KisPaintInformation::drawingSpeed() const { return d->speed; } qreal KisPaintInformation::rotation() const { return d->rotation; } qreal KisPaintInformation::tangentialPressure() const { return d->tangentialPressure; } qreal KisPaintInformation::perspective() const { return d->perspective; } qreal KisPaintInformation::currentTime() const { return d->time; } KisRandomSourceSP KisPaintInformation::randomSource() const { if (!d->randomSource) { d->randomSource = new KisRandomSource(); } return d->randomSource; } void KisPaintInformation::setRandomSource(KisRandomSourceSP value) { d->randomSource = value; } void KisPaintInformation::setLevelOfDetail(int levelOfDetail) const { d->levelOfDetail = levelOfDetail; } QDebug operator<<(QDebug dbg, const KisPaintInformation &info) { #ifdef NDEBUG Q_UNUSED(info); #else dbg.nospace() << "Position: " << info.pos(); dbg.nospace() << ", Pressure: " << info.pressure(); dbg.nospace() << ", X Tilt: " << info.xTilt(); dbg.nospace() << ", Y Tilt: " << info.yTilt(); dbg.nospace() << ", Rotation: " << info.rotation(); dbg.nospace() << ", Tangential Pressure: " << info.tangentialPressure(); dbg.nospace() << ", Perspective: " << info.perspective(); dbg.nospace() << ", Drawing Angle: " << info.drawingAngle(); dbg.nospace() << ", Drawing Speed: " << info.drawingSpeed(); dbg.nospace() << ", Drawing Distance: " << info.drawingDistance(); dbg.nospace() << ", Time: " << info.currentTime(); #endif return dbg.space(); } KisPaintInformation KisPaintInformation::mixOnlyPosition(qreal t, const KisPaintInformation& mixedPi, const KisPaintInformation& basePi) { QPointF pt = (1 - t) * mixedPi.pos() + t * basePi.pos(); return mixImpl(pt, t, mixedPi, basePi, true, false); } KisPaintInformation KisPaintInformation::mix(qreal t, const KisPaintInformation& pi1, const KisPaintInformation& pi2) { QPointF pt = (1 - t) * pi1.pos() + t * pi2.pos(); return mix(pt, t, pi1, pi2); } KisPaintInformation KisPaintInformation::mix(const QPointF& p, qreal t, const KisPaintInformation& pi1, const KisPaintInformation& pi2) { return mixImpl(p, t, pi1, pi2, false, true); } KisPaintInformation KisPaintInformation::mixWithoutTime(qreal t, const KisPaintInformation& pi1, const KisPaintInformation& pi2) { QPointF pt = (1 - t) * pi1.pos() + t * pi2.pos(); return mixWithoutTime(pt, t, pi1, pi2); } KisPaintInformation KisPaintInformation::mixWithoutTime(const QPointF& p, qreal t, const KisPaintInformation& pi1, const KisPaintInformation& pi2) { return mixImpl(p, t, pi1, pi2, false, false); } KisPaintInformation KisPaintInformation::mixImpl(const QPointF &p, qreal t, const KisPaintInformation &pi1, const KisPaintInformation &pi2, bool posOnly, bool mixTime) { if (posOnly) { KisPaintInformation result(p, pi2.pressure(), pi2.xTilt(), pi2.yTilt(), pi2.rotation(), pi2.tangentialPressure(), pi2.perspective(), pi2.currentTime(), pi2.drawingSpeed()); result.setRandomSource(pi2.randomSource()); return result; } else { qreal pressure = (1 - t) * pi1.pressure() + t * pi2.pressure(); qreal xTilt = (1 - t) * pi1.xTilt() + t * pi2.xTilt(); qreal yTilt = (1 - t) * pi1.yTilt() + t * pi2.yTilt(); qreal rotation = pi1.rotation(); if (pi1.rotation() != pi2.rotation()) { qreal a1 = kisDegreesToRadians(pi1.rotation()); qreal a2 = kisDegreesToRadians(pi2.rotation()); qreal distance = shortestAngularDistance(a2, a1); rotation = kisRadiansToDegrees(incrementInDirection(a1, t * distance, a2)); } qreal tangentialPressure = (1 - t) * pi1.tangentialPressure() + t * pi2.tangentialPressure(); qreal perspective = (1 - t) * pi1.perspective() + t * pi2.perspective(); qreal time = mixTime ? ((1 - t) * pi1.currentTime() + t * pi2.currentTime()) : pi2.currentTime(); qreal speed = (1 - t) * pi1.drawingSpeed() + t * pi2.drawingSpeed(); KisPaintInformation result(p, pressure, xTilt, yTilt, rotation, tangentialPressure, perspective, time, speed); KIS_ASSERT_RECOVER_NOOP(pi1.isHoveringMode() == pi2.isHoveringMode()); result.d->isHoveringMode = pi1.isHoveringMode(); result.d->levelOfDetail = pi1.d->levelOfDetail; result.d->randomSource = pi1.d->randomSource; result.d->canvasRotation = pi2.canvasRotation(); result.d->canvasMirroredH = pi2.canvasMirroredH(); return result; } } qreal KisPaintInformation::tiltDirection(const KisPaintInformation& info, bool normalize) { qreal xTilt = info.xTilt(); qreal yTilt = info.yTilt(); // radians -PI, PI qreal tiltDirection = atan2(-xTilt, yTilt); // if normalize is true map to 0.0..1.0 return normalize ? (tiltDirection / (2 * M_PI) + 0.5) : tiltDirection; } qreal KisPaintInformation::tiltElevation(const KisPaintInformation& info, qreal maxTiltX, qreal maxTiltY, bool normalize) { qreal xTilt = qBound(qreal(-1.0), info.xTilt() / maxTiltX , qreal(1.0)); qreal yTilt = qBound(qreal(-1.0), info.yTilt() / maxTiltY , qreal(1.0)); qreal e; if (fabs(xTilt) > fabs(yTilt)) { e = sqrt(qreal(1.0) + yTilt * yTilt); } else { e = sqrt(qreal(1.0) + xTilt * xTilt); } qreal cosAlpha = sqrt(xTilt * xTilt + yTilt * yTilt) / e; qreal tiltElevation = acos(cosAlpha); // in radians in [0, 0.5 * PI] // mapping to 0.0..1.0 if normalize is true return normalize ? (tiltElevation / (M_PI * qreal(0.5))) : tiltElevation; } diff --git a/libs/image/brushengine/kis_paint_information.h b/libs/image/brushengine/kis_paint_information.h index 0a08a9cbb7..c0b69d86c5 100644 --- a/libs/image/brushengine/kis_paint_information.h +++ b/libs/image/brushengine/kis_paint_information.h @@ -1,282 +1,283 @@ /* * Copyright (c) 2004 Cyrille Berger * Copyright (c) 2006 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef _KIS_PAINT_INFORMATION_ #define _KIS_PAINT_INFORMATION_ #include #include #include "kis_global.h" #include "kritaimage_export.h" #include #include "kis_random_source.h" class QDomDocument; class QDomElement; class KisDistanceInformation; /** * KisPaintInformation contains information about the input event that * causes the brush action to happen to the brush engine's paint * methods. * * XXX: we directly pass the KoPointerEvent x and y tilt to * KisPaintInformation, and their range is -60 to +60! * * @param pos: the position of the paint event in subpixel accuracy * @param pressure: the pressure of the stylus * @param xTilt: the angle between the device (a pen, for example) and * the perpendicular in the direction of the x axis. Positive values * are towards the bottom of the tablet. The angle is within the range * 0 to 1 * @param yTilt: the angle between the device (a pen, for example) and * the perpendicular in the direction of the y axis. Positive values * are towards the bottom of the tablet. The angle is within the range * 0 to . * @param movement: current position minus the last position of the call to paintAt * @param rotation * @param tangentialPressure * @param perspective **/ class KRITAIMAGE_EXPORT KisPaintInformation { public: /** * Note, that this class is relied on the compiler optimization * of the return value. So if it doesn't work for some reason, * please implement a proper copy c-tor */ class KRITAIMAGE_EXPORT DistanceInformationRegistrar { public: DistanceInformationRegistrar(KisPaintInformation *_p, KisDistanceInformation *distanceInfo); ~DistanceInformationRegistrar(); private: KisPaintInformation *p; }; public: /** * Create a new KisPaintInformation object. */ KisPaintInformation(const QPointF & pos, qreal pressure, qreal xTilt, qreal yTilt, qreal rotation, qreal tangentialPressure, qreal perspective, qreal time, qreal speed); KisPaintInformation(const QPointF & pos, qreal pressure, qreal xTilt, qreal yTilt, qreal rotation); KisPaintInformation(const QPointF & pos = QPointF(), qreal pressure = PRESSURE_DEFAULT); KisPaintInformation(const KisPaintInformation& rhs); void operator=(const KisPaintInformation& rhs); ~KisPaintInformation(); template void paintAt(PaintOp &op, KisDistanceInformation *distanceInfo) { KisSpacingInformation spacingInfo; - + KisTimingInformation timingInfo; { DistanceInformationRegistrar r = registerDistanceInformation(distanceInfo); spacingInfo = op.paintAt(*this); + timingInfo = op.updateTimingImpl(*this); } - distanceInfo->registerPaintedDab(*this, spacingInfo); + distanceInfo->registerPaintedDab(*this, spacingInfo, timingInfo); } const QPointF& pos() const; void setPos(const QPointF& p); /// The pressure of the value (from 0.0 to 1.0) qreal pressure() const; /// Set the pressure void setPressure(qreal p); /// The tilt of the pen on the horizontal axis (from 0.0 to 1.0) qreal xTilt() const; /// The tilt of the pen on the vertical axis (from 0.0 to 1.0) qreal yTilt() const; /// XXX !!! :-| Please add dox! void overrideDrawingAngle(qreal angle); /// XXX !!! :-| Please add dox! qreal drawingAngleSafe(const KisDistanceInformation &distance) const; /** * Causes the specified distance information to be temporarily registered with this * KisPaintInformation object, so that the KisPaintInformation can compute certain values that * may be needed at painting time, such as the drawing direction. When the returned object is * destroyed, the KisDistanceInformation will be unregistered. At most one * KisDistanceInformation can be registered with a given KisPaintInformation at a time. */ DistanceInformationRegistrar registerDistanceInformation(KisDistanceInformation *distance); /** * Current brush direction computed from the cursor movement * * WARNING: this method is available *only* inside paintAt() call, * that is when the distance information is registered. */ qreal drawingAngle() const; /** * Lock current drawing angle for the rest of the stroke. If some * value has already been locked, \p alpha shown the coefficient * with which the new velue should be blended in. */ void lockCurrentDrawingAngle(qreal alpha) const; /** * Current brush direction vector computed from the cursor movement * * WARNING: this method is available *only* inside paintAt() call, * that is when the distance information is registered. */ QPointF drawingDirectionVector() const; /** * Current brush speed computed from the cursor movement * * WARNING: this method is available *only* inside paintAt() call, * that is when the distance information is registered. */ qreal drawingSpeed() const; /** * Current distance from the previous dab * * WARNING: this method is available *only* inside paintAt() call, * that is when the distance information is registered. */ qreal drawingDistance() const; /// rotation as given by the tablet event qreal rotation() const; /// tangential pressure (i.e., rate for an airbrush device) qreal tangentialPressure() const; /// reciprocal of distance on the perspective grid qreal perspective() const; /// Number of ms since the beginning of the stroke qreal currentTime() const; // random source for generating in-stroke effects KisRandomSourceSP randomSource() const; // the stroke should initialize random source of all the used // paint info objects, otherwise it shows a warning void setRandomSource(KisRandomSourceSP value); // set level of detail which info object has been generated for void setLevelOfDetail(int levelOfDetail) const; /** * The paint information may be generated not only during real * stroke when the actual painting is happening, but also when the * cursor is hovering the canvas. In this mode some of the sensors * work a bit differently. The most outstanding example is Fuzzy * sensor, which returns unit value in this mode, otherwise it is * too irritating for a user. * * This value is true only for paint information objects created with * createHoveringModeInfo() constructor. * * \see createHoveringModeInfo() */ bool isHoveringMode() const; /** * Create a fake info object with isHoveringMode() property set to * true. * * \see isHoveringMode() */ static KisPaintInformation createHoveringModeInfo(const QPointF &pos, qreal pressure = PRESSURE_DEFAULT, qreal xTilt = 0.0, qreal yTilt = 0.0, qreal rotation = 0.0, qreal tangentialPressure = 0.0, qreal perspective = 1.0, qreal speed = 0.0, int canvasrotation = 0, bool canvasMirroredH = false); /** *Returns the canvas rotation if that has been given to the kispaintinformation. */ int canvasRotation() const; /** *set the canvas rotation. */ void setCanvasRotation(int rotation); /* *Whether the canvas is mirrored for the paint-operation. */ bool canvasMirroredH() const; /* *Set whether the canvas is mirrored for the paint-operation. */ void setCanvasHorizontalMirrorState(bool mir); void toXML(QDomDocument&, QDomElement&) const; static KisPaintInformation fromXML(const QDomElement&); /// (1-t) * p1 + t * p2 static KisPaintInformation mixOnlyPosition(qreal t, const KisPaintInformation& mixedPi, const KisPaintInformation& basePi); static KisPaintInformation mix(const QPointF& p, qreal t, const KisPaintInformation& p1, const KisPaintInformation& p2); static KisPaintInformation mix(qreal t, const KisPaintInformation& pi1, const KisPaintInformation& pi2); static KisPaintInformation mixWithoutTime(const QPointF &p, qreal t, const KisPaintInformation &p1, const KisPaintInformation &p2); static KisPaintInformation mixWithoutTime(qreal t, const KisPaintInformation &pi1, const KisPaintInformation &pi2); static qreal tiltDirection(const KisPaintInformation& info, bool normalize = true); static qreal tiltElevation(const KisPaintInformation& info, qreal maxTiltX = 60.0, qreal maxTiltY = 60.0, bool normalize = true); private: static KisPaintInformation mixImpl(const QPointF &p, qreal t, const KisPaintInformation &p1, const KisPaintInformation &p2, bool posOnly, bool mixTime); private: struct Private; Private* const d; }; KRITAIMAGE_EXPORT QDebug operator<<(QDebug debug, const KisPaintInformation& info); #endif diff --git a/libs/image/brushengine/kis_paintop.cc b/libs/image/brushengine/kis_paintop.cc index 7b6e6cc4b4..24b1df7d35 100644 --- a/libs/image/brushengine/kis_paintop.cc +++ b/libs/image/brushengine/kis_paintop.cc @@ -1,185 +1,205 @@ /* * Copyright (c) 2002 Patrick Julien * Copyright (c) 2004 Boudewijn Rempt * Copyright (c) 2004 Clarence Dang * Copyright (c) 2004 Adrian Page * Copyright (c) 2004,2007,2010 Cyrille Berger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_paintop.h" #include #include #include #include #include "kis_painter.h" #include "kis_layer.h" #include "kis_image.h" #include "kis_paint_device.h" #include "kis_global.h" #include "kis_datamanager.h" #include #include #include "kis_vec.h" #include "kis_perspective_math.h" #include "kis_fixed_paint_device.h" #include "kis_paintop_utils.h" #define BEZIER_FLATNESS_THRESHOLD 0.5 #include #include struct Q_DECL_HIDDEN KisPaintOp::Private { Private(KisPaintOp *_q) : q(_q), dab(0), fanCornersEnabled(false), fanCornersStep(1.0) {} KisPaintOp *q; KisFixedPaintDeviceSP dab; KisPainter* painter; bool fanCornersEnabled; qreal fanCornersStep; }; KisPaintOp::KisPaintOp(KisPainter * painter) : d(new Private(this)) { d->painter = painter; } KisPaintOp::~KisPaintOp() { d->dab.clear(); delete d; } KisFixedPaintDeviceSP KisPaintOp::cachedDab() { return cachedDab(d->painter->device()->colorSpace()); } KisFixedPaintDeviceSP KisPaintOp::cachedDab(const KoColorSpace *cs) { if (!d->dab || *d->dab->colorSpace() != *cs) { d->dab = new KisFixedPaintDevice(cs); } return d->dab; } void KisPaintOp::setFanCornersInfo(bool fanCornersEnabled, qreal fanCornersStep) { d->fanCornersEnabled = fanCornersEnabled; d->fanCornersStep = fanCornersStep; } void KisPaintOp::splitCoordinate(qreal coordinate, qint32 *whole, qreal *fraction) { const qint32 i = std::floor(coordinate); const qreal f = coordinate - i; *whole = i; *fraction = f; } static void paintBezierCurve(KisPaintOp *paintOp, const KisPaintInformation &pi1, const KisVector2D &control1, const KisVector2D &control2, const KisPaintInformation &pi2, KisDistanceInformation *currentDistance) { LineEquation line = LineEquation::Through(toKisVector2D(pi1.pos()), toKisVector2D(pi2.pos())); qreal d1 = line.absDistance(control1); qreal d2 = line.absDistance(control2); if ((d1 < BEZIER_FLATNESS_THRESHOLD && d2 < BEZIER_FLATNESS_THRESHOLD) || qIsNaN(d1) || qIsNaN(d2)) { paintOp->paintLine(pi1, pi2, currentDistance); } else { // Midpoint subdivision. See Foley & Van Dam Computer Graphics P.508 KisVector2D l2 = (toKisVector2D(pi1.pos()) + control1) / 2; KisVector2D h = (control1 + control2) / 2; KisVector2D l3 = (l2 + h) / 2; KisVector2D r3 = (control2 + toKisVector2D(pi2.pos())) / 2; KisVector2D r2 = (h + r3) / 2; KisVector2D l4 = (l3 + r2) / 2; KisPaintInformation middlePI = KisPaintInformation::mix(toQPointF(l4), 0.5, pi1, pi2); paintBezierCurve(paintOp, pi1, l2, l3, middlePI, currentDistance); paintBezierCurve(paintOp, middlePI, r2, r3, pi2, currentDistance); } } void KisPaintOp::paintBezierCurve(const KisPaintInformation &pi1, const QPointF &control1, const QPointF &control2, const KisPaintInformation &pi2, KisDistanceInformation *currentDistance) { return ::paintBezierCurve(this, pi1, toKisVector2D(control1), toKisVector2D(control2), pi2, currentDistance); } void KisPaintOp::paintLine(const KisPaintInformation &pi1, const KisPaintInformation &pi2, KisDistanceInformation *currentDistance) { KisPaintOpUtils::paintLine(*this, pi1, pi2, currentDistance, d->fanCornersEnabled, d->fanCornersStep); } void KisPaintOp::paintAt(const KisPaintInformation& info, KisDistanceInformation *currentDistance) { Q_ASSERT(currentDistance); KisPaintInformation pi(info); pi.paintAt(*this, currentDistance); } void KisPaintOp::updateSpacing(const KisPaintInformation &info, KisDistanceInformation ¤tDistance) const { KisPaintInformation pi(info); KisSpacingInformation spacingInfo; { KisPaintInformation::DistanceInformationRegistrar r = pi.registerDistanceInformation(¤tDistance); spacingInfo = updateSpacingImpl(pi); } - currentDistance.setSpacing(spacingInfo); + currentDistance.updateSpacing(spacingInfo); +} + +void KisPaintOp::updateTiming(const KisPaintInformation &info, + KisDistanceInformation ¤tDistance) const +{ + KisPaintInformation pi(info); + KisTimingInformation timingInfo; + { + KisPaintInformation::DistanceInformationRegistrar r + = pi.registerDistanceInformation(¤tDistance); + timingInfo = updateTimingImpl(pi); + } + + currentDistance.updateTiming(timingInfo); +} + +KisTimingInformation KisPaintOp::updateTimingImpl(const KisPaintInformation &info) const +{ + Q_UNUSED(info); + return KisTimingInformation(); } KisPainter* KisPaintOp::painter() const { return d->painter; } KisPaintDeviceSP KisPaintOp::source() const { return d->painter->device(); } diff --git a/libs/image/brushengine/kis_paintop.h b/libs/image/brushengine/kis_paintop.h index b6bbee5cc2..e97639249b 100644 --- a/libs/image/brushengine/kis_paintop.h +++ b/libs/image/brushengine/kis_paintop.h @@ -1,142 +1,155 @@ /* * Copyright (c) 2002 Patrick Julien * Copyright (c) 2004 Boudewijn Rempt * Copyright (c) 2004 Clarence Dang * Copyright (c) 2004 Adrian Page * Copyright (c) 2004,2010 Cyrille Berger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_PAINTOP_H_ #define KIS_PAINTOP_H_ #include #include "kis_shared.h" #include "kis_types.h" #include class QPointF; class KoColorSpace; class KisPainter; class KisPaintInformation; /** * KisPaintOp are use by tools to draw on a paint device. A paintop takes settings * and input information, like pressure, tilt or motion and uses that to draw pixels */ class KRITAIMAGE_EXPORT KisPaintOp : public KisShared { struct Private; public: KisPaintOp(KisPainter * painter); virtual ~KisPaintOp(); /** - * Paint at the subpixel point pos using the specified paint - * information.. + * Paint at the subpixel point pos using the specified paint information.. * - * The distance between two calls of the paintAt is always - * specified by spacing, which is automatically saved into the - * current distance information object + * The distance/time between two calls of the paintAt is always specified by spacing and timing, + * which are automatically saved into the current distance information object. */ void paintAt(const KisPaintInformation& info, KisDistanceInformation *currentDistance); /** - * Updates the spacing in currentDistance based on the provided information. Note that the - * spacing is updated automatically in the paintAt method, so there is no need to call this + * Updates the spacing settings in currentDistance based on the provided information. Note that + * the spacing is updated automatically in the paintAt method, so there is no need to call this * method if paintAt has just been called. */ void updateSpacing(const KisPaintInformation &info, KisDistanceInformation ¤tDistance) const; + /** + * Updates the timing settings in currentDistance based on the provided information. Note that + * the timing is updated automatically in the paintAt method, so there is no need to call this + * method if paintAt has just been called. + */ + void updateTiming(const KisPaintInformation &info, KisDistanceInformation ¤tDistance) + const; + /** * Draw a line between pos1 and pos2 using the currently set brush and color. * If savedDist is less than zero, the brush is painted at pos1 before being * painted along the line using the spacing setting. * * @return the drag distance, that is the remains of the distance * between p1 and p2 not covered because the currenlty set brush * has a spacing greater than that distance. */ virtual void paintLine(const KisPaintInformation &pi1, const KisPaintInformation &pi2, KisDistanceInformation *currentDistance); /** * Draw a Bezier curve between pos1 and pos2 using control points 1 and 2. * If savedDist is less than zero, the brush is painted at pos1 before being * painted along the curve using the spacing setting. * @return the drag distance, that is the remains of the distance between p1 and p2 not covered * because the currenlty set brush has a spacing greater than that distance. */ virtual void paintBezierCurve(const KisPaintInformation &pi1, const QPointF &control1, const QPointF &control2, const KisPaintInformation &pi2, KisDistanceInformation *currentDistance); /** * Whether this paintop can paint. Can be false in case that some setting isn't read correctly. * @return if paintop is ready for painting, default is true */ virtual bool canPaint() const { return true; } /** * Split the coordinate into whole + fraction, where fraction is always >= 0. */ static void splitCoordinate(qreal coordinate, qint32 *whole, qreal *fraction); protected: friend class KisPaintInformation; /** - * The implementation of painting of a dab and updating spacing + * The implementation of painting of a dab and updating spacing. This does NOT need to update + * the timing information. */ virtual KisSpacingInformation paintAt(const KisPaintInformation& info) = 0; /** * Implementation of a spacing update */ virtual KisSpacingInformation updateSpacingImpl(const KisPaintInformation &info) const = 0; + /** + * Implementation of a timing update. The default implementation always disables timing. This is + * suitable for paintops that do not support airbrushing. + */ + virtual KisTimingInformation updateTimingImpl(const KisPaintInformation &info) const; + KisFixedPaintDeviceSP cachedDab(); KisFixedPaintDeviceSP cachedDab(const KoColorSpace *cs); /** * Return the painter this paintop is owned by */ KisPainter* painter() const; /** * Return the paintdevice the painter this paintop is owned by */ KisPaintDeviceSP source() const; private: friend class KisPressureRotationOption; void setFanCornersInfo(bool fanCornersEnabled, qreal fanCornersStep); private: Private* const d; }; #endif // KIS_PAINTOP_H_ diff --git a/libs/image/brushengine/kis_paintop_settings.cpp b/libs/image/brushengine/kis_paintop_settings.cpp index be3f1678e7..c6680e195a 100644 --- a/libs/image/brushengine/kis_paintop_settings.cpp +++ b/libs/image/brushengine/kis_paintop_settings.cpp @@ -1,450 +1,456 @@ /* * Copyright (c) 2007 Boudewijn Rempt * Copyright (c) 2008 Lukáš Tvrdý * Copyright (c) 2014 Mohit Goyal * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include #include #include #include "kis_paint_layer.h" #include "kis_image.h" #include "kis_painter.h" #include "kis_paint_device.h" #include "kis_paintop_registry.h" +#include "kis_timing_information.h" #include #include "kis_paintop_config_widget.h" #include #include "kis_paintop_settings_update_proxy.h" #include #include #include #include #include struct Q_DECL_HIDDEN KisPaintOpSettings::Private { Private() : disableDirtyNotifications(false) {} QPointer settingsWidget; QString modelName; KisPaintOpPresetWSP preset; QList uniformProperties; bool disableDirtyNotifications; class DirtyNotificationsLocker { public: DirtyNotificationsLocker(KisPaintOpSettings::Private *d) : m_d(d), m_oldNotificationsState(d->disableDirtyNotifications) { m_d->disableDirtyNotifications = true; } ~DirtyNotificationsLocker() { m_d->disableDirtyNotifications = m_oldNotificationsState; } private: KisPaintOpSettings::Private *m_d; bool m_oldNotificationsState; Q_DISABLE_COPY(DirtyNotificationsLocker) }; KisPaintopSettingsUpdateProxy* updateProxyNoCreate() const { auto presetSP = preset.toStrongRef(); return presetSP ? presetSP->updateProxyNoCreate() : 0; } KisPaintopSettingsUpdateProxy* updateProxyCreate() const { auto presetSP = preset.toStrongRef(); return presetSP ? presetSP->updateProxy() : 0; } }; KisPaintOpSettings::KisPaintOpSettings() : d(new Private) { d->preset = 0; } KisPaintOpSettings::~KisPaintOpSettings() { } KisPaintOpSettings::KisPaintOpSettings(const KisPaintOpSettings &rhs) : KisPropertiesConfiguration(rhs) , d(new Private) { d->settingsWidget = 0; d->preset = rhs.preset(); d->modelName = rhs.modelName(); } void KisPaintOpSettings::setOptionsWidget(KisPaintOpConfigWidget* widget) { d->settingsWidget = widget; } void KisPaintOpSettings::setPreset(KisPaintOpPresetWSP preset) { d->preset = preset; } KisPaintOpPresetWSP KisPaintOpSettings::preset() const { return d->preset; } bool KisPaintOpSettings::mousePressEvent(const KisPaintInformation &paintInformation, Qt::KeyboardModifiers modifiers, KisNodeWSP currentNode) { Q_UNUSED(modifiers); Q_UNUSED(currentNode); setRandomOffset(paintInformation); return true; // ignore the event by default } void KisPaintOpSettings::setRandomOffset(const KisPaintInformation &paintInformation) { if (getBool("Texture/Pattern/Enabled")) { if (getBool("Texture/Pattern/isRandomOffsetX")) { setProperty("Texture/Pattern/OffsetX", paintInformation.randomSource()->generate(0, KisPropertiesConfiguration::getInt("Texture/Pattern/MaximumOffsetX"))); } if (getBool("Texture/Pattern/isRandomOffsetY")) { setProperty("Texture/Pattern/OffsetY", paintInformation.randomSource()->generate(0, KisPropertiesConfiguration::getInt("Texture/Pattern/MaximumOffsetY"))); } } } KisPaintOpSettingsSP KisPaintOpSettings::clone() const { QString paintopID = getString("paintop"); if (paintopID.isEmpty()) return 0; KisPaintOpSettingsSP settings = KisPaintOpRegistry::instance()->settings(KoID(paintopID, "")); QMapIterator i(getProperties()); while (i.hasNext()) { i.next(); settings->setProperty(i.key(), QVariant(i.value())); } settings->setPreset(this->preset()); return settings; } void KisPaintOpSettings::activate() { } void KisPaintOpSettings::setPaintOpOpacity(qreal value) { KisLockedPropertiesProxySP proxy( KisLockedPropertiesServer::instance()->createLockedPropertiesProxy(this)); proxy->setProperty("OpacityValue", value); } void KisPaintOpSettings::setPaintOpFlow(qreal value) { KisLockedPropertiesProxySP proxy( KisLockedPropertiesServer::instance()->createLockedPropertiesProxy(this)); proxy->setProperty("FlowValue", value); } void KisPaintOpSettings::setPaintOpCompositeOp(const QString &value) { KisLockedPropertiesProxySP proxy( KisLockedPropertiesServer::instance()->createLockedPropertiesProxy(this)); proxy->setProperty("CompositeOp", value); } qreal KisPaintOpSettings::paintOpOpacity() { KisLockedPropertiesProxySP proxy = KisLockedPropertiesServer::instance()->createLockedPropertiesProxy(this); return proxy->getDouble("OpacityValue", 1.0); } qreal KisPaintOpSettings::paintOpFlow() { KisLockedPropertiesProxySP proxy( KisLockedPropertiesServer::instance()->createLockedPropertiesProxy(this)); return proxy->getDouble("FlowValue", 1.0); } QString KisPaintOpSettings::paintOpCompositeOp() { KisLockedPropertiesProxySP proxy( KisLockedPropertiesServer::instance()->createLockedPropertiesProxy(this)); return proxy->getString("CompositeOp", COMPOSITE_OVER); } void KisPaintOpSettings::setEraserMode(bool value) { KisLockedPropertiesProxySP proxy( KisLockedPropertiesServer::instance()->createLockedPropertiesProxy(this)); proxy->setProperty("EraserMode", value); } bool KisPaintOpSettings::eraserMode() { KisLockedPropertiesProxySP proxy( KisLockedPropertiesServer::instance()->createLockedPropertiesProxy(this)); return proxy->getBool("EraserMode", false); } QString KisPaintOpSettings::effectivePaintOpCompositeOp() { return !eraserMode() ? paintOpCompositeOp() : COMPOSITE_ERASE; } qreal KisPaintOpSettings::savedEraserSize() const { return getDouble("SavedEraserSize", 0.0); } void KisPaintOpSettings::setSavedEraserSize(qreal value) { setProperty("SavedEraserSize", value); setPropertyNotSaved("SavedEraserSize"); } qreal KisPaintOpSettings::savedBrushSize() const { return getDouble("SavedBrushSize", 0.0); } void KisPaintOpSettings::setSavedBrushSize(qreal value) { setProperty("SavedBrushSize", value); setPropertyNotSaved("SavedBrushSize"); } qreal KisPaintOpSettings::savedEraserOpacity() const { return getDouble("SavedEraserOpacity", 0.0); } void KisPaintOpSettings::setSavedEraserOpacity(qreal value) { setProperty("SavedEraserOpacity", value); setPropertyNotSaved("SavedEraserOpacity"); } qreal KisPaintOpSettings::savedBrushOpacity() const { return getDouble("SavedBrushOpacity", 0.0); } void KisPaintOpSettings::setSavedBrushOpacity(qreal value) { setProperty("SavedBrushOpacity", value); setPropertyNotSaved("SavedBrushOpacity"); } QString KisPaintOpSettings::modelName() const { return d->modelName; } void KisPaintOpSettings::setModelName(const QString & modelName) { d->modelName = modelName; } KisPaintOpConfigWidget* KisPaintOpSettings::optionsWidget() const { if (d->settingsWidget.isNull()) return 0; return d->settingsWidget.data(); } bool KisPaintOpSettings::isValid() const { return true; } bool KisPaintOpSettings::isLoadable() { return isValid(); } QString KisPaintOpSettings::indirectPaintingCompositeOp() const { return COMPOSITE_ALPHA_DARKEN; } bool KisPaintOpSettings::isAirbrushing() const { return getBool(AIRBRUSH_ENABLED, false); } qreal KisPaintOpSettings::airbrushInterval() const { qreal rate = getDouble(AIRBRUSH_RATE, 1.0); if (rate == 0.0) { - return 1000.0; + return LONG_TIME; } else { return 1000.0 / rate; } } +bool KisPaintOpSettings::useSpacingUpdates() const +{ + return getBool(SPACING_USE_UPDATES, false); +} + QPainterPath KisPaintOpSettings::brushOutline(const KisPaintInformation &info, OutlineMode mode) { QPainterPath path; if (mode == CursorIsOutline || mode == CursorIsCircleOutline || mode == CursorTiltOutline) { path = ellipseOutline(10, 10, 1.0, 0); if (mode == CursorTiltOutline) { path.addPath(makeTiltIndicator(info, QPointF(0.0, 0.0), 0.0, 2.0)); } path.translate(info.pos()); } return path; } QPainterPath KisPaintOpSettings::ellipseOutline(qreal width, qreal height, qreal scale, qreal rotation) { QPainterPath path; QRectF ellipse(0, 0, width * scale, height * scale); ellipse.translate(-ellipse.center()); path.addEllipse(ellipse); QTransform m; m.reset(); m.rotate(rotation); path = m.map(path); return path; } QPainterPath KisPaintOpSettings::makeTiltIndicator(KisPaintInformation const& info, QPointF const& start, qreal maxLength, qreal angle) { if (maxLength == 0.0) maxLength = 50.0; maxLength = qMax(maxLength, 50.0); qreal const length = maxLength * (1 - info.tiltElevation(info, 60.0, 60.0, true)); qreal const baseAngle = 360.0 - fmod(KisPaintInformation::tiltDirection(info, true) * 360.0 + 270.0, 360.0); QLineF guideLine = QLineF::fromPolar(length, baseAngle + angle); guideLine.translate(start); QPainterPath ret; ret.moveTo(guideLine.p1()); ret.lineTo(guideLine.p2()); guideLine.setAngle(baseAngle - angle); ret.lineTo(guideLine.p2()); ret.lineTo(guideLine.p1()); return ret; } void KisPaintOpSettings::setCanvasRotation(qreal angle) { Private::DirtyNotificationsLocker locker(d.data()); setProperty("runtimeCanvasRotation", angle); setPropertyNotSaved("runtimeCanvasRotation"); } void KisPaintOpSettings::setCanvasMirroring(bool xAxisMirrored, bool yAxisMirrored) { Private::DirtyNotificationsLocker locker(d.data()); setProperty("runtimeCanvasMirroredX", xAxisMirrored); setPropertyNotSaved("runtimeCanvasMirroredX"); setProperty("runtimeCanvasMirroredY", yAxisMirrored); setPropertyNotSaved("runtimeCanvasMirroredY"); } void KisPaintOpSettings::setProperty(const QString & name, const QVariant & value) { if (value != KisPropertiesConfiguration::getProperty(name) && !d->disableDirtyNotifications) { KisPaintOpPresetSP presetSP = preset().toStrongRef(); if (presetSP) { presetSP->setPresetDirty(true); } } KisPropertiesConfiguration::setProperty(name, value); onPropertyChanged(); } void KisPaintOpSettings::onPropertyChanged() { KisPaintopSettingsUpdateProxy *proxy = d->updateProxyNoCreate(); if (proxy) { proxy->notifySettingsChanged(); } } bool KisPaintOpSettings::isLodUserAllowed(const KisPropertiesConfigurationSP config) { return config->getBool("lodUserAllowed", true); } void KisPaintOpSettings::setLodUserAllowed(KisPropertiesConfigurationSP config, bool value) { config->setProperty("lodUserAllowed", value); } #include "kis_standard_uniform_properties_factory.h" QList KisPaintOpSettings::uniformProperties(KisPaintOpSettingsSP settings) { QList props = listWeakToStrong(d->uniformProperties); if (props.isEmpty()) { using namespace KisStandardUniformPropertiesFactory; props.append(createProperty(opacity, settings, d->updateProxyCreate())); props.append(createProperty(size, settings, d->updateProxyCreate())); props.append(createProperty(flow, settings, d->updateProxyCreate())); d->uniformProperties = listStrongToWeak(props); } return props; } diff --git a/libs/image/brushengine/kis_paintop_settings.h b/libs/image/brushengine/kis_paintop_settings.h index 1d9ac1a280..7ed5fb2e5c 100644 --- a/libs/image/brushengine/kis_paintop_settings.h +++ b/libs/image/brushengine/kis_paintop_settings.h @@ -1,303 +1,315 @@ /* * Copyright (c) 2007 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_PAINTOP_SETTINGS_H_ #define KIS_PAINTOP_SETTINGS_H_ #include "kis_types.h" #include "kritaimage_export.h" #include #include #include "kis_properties_configuration.h" #include #include class KisPaintOpConfigWidget; class KisPaintopSettingsUpdateProxy; /** * Configuration property used to control whether airbrushing is enabled. */ const QString AIRBRUSH_ENABLED = "PaintOpSettings/isAirbrushing"; /** * Configuration property used to control airbrushing rate. The value should be in dabs per second. */ const QString AIRBRUSH_RATE = "PaintOpSettings/rate"; /** * Configuration property used to control whether airbrushing is configured to ignore distance-based * spacing. */ const QString AIRBRUSH_IGNORE_SPACING = "PaintOpSettings/ignoreSpacing"; +/** + * Configuration property used to control whether the spacing settings can be updated between + * painted dabs. + */ +const QString SPACING_USE_UPDATES = "PaintOpSettings/updateSpacingBetweenDabs"; + /** * This class is used to cache the settings for a paintop * between two creations. There is one KisPaintOpSettings per input device (mouse, tablet, * etc...). * * The settings may be stored in a preset or a recorded brush stroke. Note that if your * paintop's settings subclass has data that is not stored as a property, that data is not * saved and restored. * * The object also contains a pointer to its parent KisPaintOpPreset object.This is to control the DirtyPreset * property of KisPaintOpPreset. Whenever the settings are changed/modified from the original -- the preset is * set to dirty. */ class KRITAIMAGE_EXPORT KisPaintOpSettings : public KisPropertiesConfiguration { public: KisPaintOpSettings(); ~KisPaintOpSettings() override; KisPaintOpSettings(const KisPaintOpSettings &rhs); /** * */ virtual void setOptionsWidget(KisPaintOpConfigWidget* widget); /** * This function is called by a tool when the mouse is pressed. It's useful if * the paintop needs mouse interaction for instance in the case of the clone op. * If the tool is supposed to ignore the event, the paint op should return false * and if the tool is supposed to use the event, return true. */ virtual bool mousePressEvent(const KisPaintInformation &paintInformation, Qt::KeyboardModifiers modifiers, KisNodeWSP currentNode); /** * Clone the current settings object. Override this if your settings instance doesn't * store everything as properties. */ virtual KisPaintOpSettingsSP clone() const; /** * @return the node the paintop is working on. */ KisNodeSP node() const; /** * Call this function when the paint op is selected or the tool is activated */ virtual void activate(); /** * XXX: Remove this after 2.0, when the paint operation (incremental/non incremental) will * be completely handled in the paintop, not in the tool. This is a filthy hack to move * the option to the right place, at least. * @return true if we paint incrementally, false if we paint like Photoshop. By default, paintops * do not support non-incremental. */ virtual bool paintIncremental() { return true; } /** * @return the composite op it to which the indirect painting device * should be initialized to. This is used by clone op to reset * the composite op to COMPOSITE_COPY */ virtual QString indirectPaintingCompositeOp() const; /** * Whether this paintop wants to deposit paint even when not moving, i.e. the tool needs to * activate its timer. If this is true, painting updates need to be generated at regular * intervals even in the absence of input device events, e.g. when the cursor is not moving. * * The default implementation checks the property AIRBRUSH_ENABLED, defaulting to false if the * property is not found. This should be suitable for most paintops. */ virtual bool isAirbrushing() const; /** * Indicates the minimum time interval that might be needed between airbrush dabs, in * milliseconds. A lower value means painting updates need to happen more frequently. This value * should be ignored if isAirbrushing() is false. * * The default implementation uses the property AIRBRUSH_RATE, defaulting to an interval of * one second if the property is not found. This should be suitable for most paintops. */ virtual qreal airbrushInterval() const; + /** + * Indicates whether this configuration allows spacing information to be updated between painted + * dabs during a stroke. + */ + virtual bool useSpacingUpdates() const; + /** * This enum defines the current mode for painting an outline. */ enum OutlineMode { CursorIsOutline = 1, ///< When this mode is set, an outline is painted around the cursor CursorIsCircleOutline, CursorNoOutline, CursorTiltOutline, CursorColorOutline }; /** * Returns the brush outline in pixel coordinates. Tool is responsible for conversion into view coordinates. * Outline mode has to be passed to the paintop which builds the outline as some paintops have to paint outline * always like clone paintop indicating the duplicate position */ virtual QPainterPath brushOutline(const KisPaintInformation &info, OutlineMode mode); /** * Helpers for drawing the brush outline */ static QPainterPath ellipseOutline(qreal width, qreal height, qreal scale, qreal rotation); /** * Helper for drawing a triangle representing the tilt of the stylus. * * @param start is the offset from the brush's outline's bounding box * @param lengthScale is used for deciding the size of the triangle. * Brush diameter or width are common choices for this. * @param angle is the angle between the two sides of the triangle. */ static QPainterPath makeTiltIndicator(KisPaintInformation const& info, QPointF const& start, qreal lengthScale, qreal angle); /** * Set paintop opacity directly in the properties */ void setPaintOpOpacity(qreal value); /** * Set paintop flow directly in the properties */ void setPaintOpFlow(qreal value); /** * Set paintop composite mode directly in the properties */ void setPaintOpCompositeOp(const QString &value); /** * @return opacity saved in the properties */ qreal paintOpOpacity(); /** * @return flow saved in the properties */ qreal paintOpFlow(); /** * @return composite mode saved in the properties */ QString paintOpCompositeOp(); /** * Set paintop size directly in the properties */ virtual void setPaintOpSize(qreal value) = 0; /** * @return size saved in the properties */ virtual qreal paintOpSize() const = 0; void setEraserMode(bool value); bool eraserMode(); qreal savedEraserSize() const; void setSavedEraserSize(qreal value); qreal savedBrushSize() const; void setSavedBrushSize(qreal value); qreal savedEraserOpacity() const; void setSavedEraserOpacity(qreal value); qreal savedBrushOpacity() const; void setSavedBrushOpacity(qreal value); QString effectivePaintOpCompositeOp(); void setPreset(KisPaintOpPresetWSP preset); KisPaintOpPresetWSP preset() const; /** * @return filename of the 3D brush model, empty if no brush is set */ virtual QString modelName() const; /** * Set filename of 3D brush model. By default no brush is set */ void setModelName(const QString & modelName); /// Check if the settings are valid, setting might be invalid through missing brushes etc /// Overwrite if the settings of a paintop can be invalid /// @return state of the settings, default implementation is true virtual bool isValid() const; /// Check if the settings are loadable, that might the case if we can fallback to something /// Overwrite if the settings can do some kind of fallback /// @return loadable state of the settings, by default implementation return the same as isValid() virtual bool isLoadable(); /** * These methods are populating properties with runtime * information about canvas rotation/mirroring. This information * is set directly by KisToolFreehand. Later the data is accessed * by the pressure options to make a final decision. */ void setCanvasRotation(qreal angle); void setCanvasMirroring(bool xAxisMirrored, bool yAxisMirrored); /** * Overrides the method in KisPropertiesCofiguration to allow * onPropertyChanged() callback */ void setProperty(const QString & name, const QVariant & value) override; virtual QList uniformProperties(KisPaintOpSettingsSP settings); static bool isLodUserAllowed(const KisPropertiesConfigurationSP config); static void setLodUserAllowed(KisPropertiesConfigurationSP config, bool value); /** * @return the option widget of the paintop (can be 0 is no option widgets is set) */ KisPaintOpConfigWidget* optionsWidget() const; /** * This function is called to set random offsets to the brush whenever the mouse is clicked. It is * specific to when the pattern option is set. * */ virtual void setRandomOffset(const KisPaintInformation &paintInformation); protected: /** * The callback is called every time when a property changes */ virtual void onPropertyChanged(); private: struct Private; const QScopedPointer d; }; #endif diff --git a/libs/image/brushengine/kis_paintop_utils.h b/libs/image/brushengine/kis_paintop_utils.h index 0c180ddc2b..ca53683483 100644 --- a/libs/image/brushengine/kis_paintop_utils.h +++ b/libs/image/brushengine/kis_paintop_utils.h @@ -1,215 +1,228 @@ /* * Copyright (c) 2014 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef __KIS_PAINTOP_UTILS_H #define __KIS_PAINTOP_UTILS_H #include "kis_global.h" #include "kis_paint_information.h" #include "kis_distance_information.h" +#include "kis_spacing_information.h" +#include "kis_timing_information.h" namespace KisPaintOpUtils { template bool paintFan(PaintOp &op, const KisPaintInformation &pi1, const KisPaintInformation &pi2, KisDistanceInformation *currentDistance, qreal fanCornersStep) { const qreal angleStep = fanCornersStep; const qreal initialAngle = currentDistance->lastDrawingAngle(); const qreal finalAngle = pi2.drawingAngleSafe(*currentDistance); const qreal fullDistance = shortestAngularDistance(initialAngle, finalAngle); qreal lastAngle = initialAngle; int i = 0; while (shortestAngularDistance(lastAngle, finalAngle) > angleStep) { lastAngle = incrementInDirection(lastAngle, angleStep, finalAngle); qreal t = angleStep * i++ / fullDistance; QPointF pt = pi1.pos() + t * (pi2.pos() - pi1.pos()); KisPaintInformation pi = KisPaintInformation::mix(pt, t, pi1, pi2); pi.overrideDrawingAngle(lastAngle); pi.paintAt(op, currentDistance); } return i; } template void paintLine(PaintOp &op, const KisPaintInformation &pi1, const KisPaintInformation &pi2, KisDistanceInformation *currentDistance, bool fanCornersEnabled, qreal fanCornersStep) { QPointF end = pi2.pos(); qreal endTime = pi2.currentTime(); KisPaintInformation pi = pi1; qreal t = 0.0; while ((t = currentDistance->getNextPointPosition(pi.pos(), end, pi.currentTime(), endTime)) >= 0.0) { pi = KisPaintInformation::mix(t, pi, pi2); if (fanCornersEnabled && currentDistance->hasLastPaintInformation()) { paintFan(op, currentDistance->lastPaintInformation(), pi, currentDistance, fanCornersStep); } /** * A bit complicated part to ensure the registration * of the distance information is done in right order */ pi.paintAt(op, currentDistance); } /* - * Perform a spacing update between dabs if appropriate. Typically, this will not happen if the - * above loop actually painted anything. This is because the getNextPointPosition() call before - * the paint operation will reset the accumulators in currentDistance and therefore make - * needsSpacingUpdate() false. The temporal distance between pi1 and pi2 is typically too small - * for the accumulators to build back up enough to require a spacing update after that. - * (The accumulated time value is updated not during the paint operation, but during the call to - * getNextPointPosition(), that is, updated during every paintLine() call.) + * Perform spacing and/or timing updates between dabs if appropriate. Typically, this will not + * happen if the above loop actually painted anything. This is because the + * getNextPointPosition() call before the paint operation will reset the accumulators in + * currentDistance and therefore make needsSpacingUpdate() and needsTimingUpdate() false. The + * temporal distance between pi1 and pi2 is typically too small for the accumulators to build + * back up enough to require a spacing or timing update after that. (The accumulated time values + * are updated not during the paint operation, but during the call to getNextPointPosition(), + * that is, updated during every paintLine() call.) */ if (currentDistance->needsSpacingUpdate()) { op.updateSpacing(pi2, *currentDistance); } + if (currentDistance->needsTimingUpdate()) { + op.updateTiming(pi2, *currentDistance); + } } /** * A special class containing the previous position of the cursor for * the sake of painting the outline of the paint op. The main purpose * of this class is to ensure that the saved point does not equal to * the current one, which would cause a outline flicker. To echieve * this the class stores two previosly requested points instead of the * last one. */ class PositionHistory { public: /** * \return the previously used point, which is guaranteed not to * be equal to \p pt and updates the history if needed */ QPointF pushThroughHistory(const QPointF &pt) { QPointF result; const qreal pointSwapThreshold = 7.0; /** * We check x *and* y separately, because events generated by * a mouse device tend to come separately for x and y offsets. * Efficienty generating the 'stairs' pattern. */ if (qAbs(pt.x() - m_second.x()) > pointSwapThreshold && qAbs(pt.y() - m_second.y()) > pointSwapThreshold) { result = m_second; m_first = m_second; m_second = pt; } else { result = m_first; } return result; } private: QPointF m_first; QPointF m_second; }; bool checkSizeTooSmall(qreal scale, qreal width, qreal height) { return scale * width < 0.01 || scale * height < 0.01; } inline qreal calcAutoSpacing(qreal value, qreal coeff) { return coeff * (value < 1.0 ? value : sqrt(value)); } QPointF calcAutoSpacing(const QPointF &pt, qreal coeff, qreal lodScale) { const qreal invLodScale = 1.0 / lodScale; const QPointF lod0Point = invLodScale * pt; return lodScale * QPointF(calcAutoSpacing(lod0Point.x(), coeff), calcAutoSpacing(lod0Point.y(), coeff)); } KisSpacingInformation effectiveSpacing(qreal dabWidth, qreal dabHeight, qreal extraScale, - qreal rateExtraScale, bool distanceSpacingEnabled, bool isotropicSpacing, qreal rotation, bool axesFlipped, qreal spacingVal, bool autoSpacingActive, qreal autoSpacingCoeff, - bool timedSpacingEnabled, - qreal timedSpacingInterval, qreal lodScale) { QPointF spacing; if (!isotropicSpacing) { if (autoSpacingActive) { spacing = calcAutoSpacing(QPointF(dabWidth, dabHeight), autoSpacingCoeff, lodScale); } else { spacing = QPointF(dabWidth, dabHeight); spacing *= spacingVal; } } else { qreal significantDimension = qMax(dabWidth, dabHeight); if (autoSpacingActive) { significantDimension = calcAutoSpacing(significantDimension, autoSpacingCoeff); } else { significantDimension *= spacingVal; } spacing = QPointF(significantDimension, significantDimension); rotation = 0.0; axesFlipped = false; } spacing *= extraScale; - qreal scaledInterval = rateExtraScale <= 0.0 ? LONG_TIME : - timedSpacingInterval / rateExtraScale; + return KisSpacingInformation(distanceSpacingEnabled, spacing, rotation, axesFlipped); +} + +KisTimingInformation effectiveTiming(bool timingEnabled, + qreal timingInterval, + qreal rateExtraScale) +{ - return KisSpacingInformation(distanceSpacingEnabled, spacing, rotation, axesFlipped, - timedSpacingEnabled, scaledInterval); + if (!timingEnabled) { + return KisTimingInformation(); + } + else { + qreal scaledInterval = rateExtraScale <= 0.0 ? LONG_TIME : timingInterval / rateExtraScale; + return KisTimingInformation(scaledInterval); + } } } #endif /* __KIS_PAINTOP_UTILS_H */ diff --git a/libs/image/kis_distance_information.cpp b/libs/image/kis_distance_information.cpp index 20dba8196a..76643df404 100644 --- a/libs/image/kis_distance_information.cpp +++ b/libs/image/kis_distance_information.cpp @@ -1,557 +1,649 @@ /* * Copyright (c) 2010 Cyrille Berger * Copyright (c) 2013 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include +#include "kis_spacing_information.h" +#include "kis_timing_information.h" #include "kis_debug.h" #include #include #include #include "kis_algebra_2d.h" #include "kis_dom_utils.h" #include "kis_lod_transform.h" const qreal MIN_DISTANCE_SPACING = 0.5; // Smallest allowed interval when timed spacing is enabled, in milliseconds. const qreal MIN_TIMED_INTERVAL = 0.5; // Largest allowed interval when timed spacing is enabled, in milliseconds. const qreal MAX_TIMED_INTERVAL = LONG_TIME; struct Q_DECL_HIDDEN KisDistanceInformation::Private { Private() : accumDistance(), accumTime(0.0), spacingUpdateInterval(LONG_TIME), + timeSinceSpacingUpdate(0.0), + timingUpdateInterval(LONG_TIME), + timeSinceTimingUpdate(0.0), lastDabInfoValid(false), lastPaintInfoValid(false), lockedDrawingAngle(0.0), hasLockedDrawingAngle(false), totalDistance(0.0) {} + // Accumulators of time/distance passed since the last painted dab QPointF accumDistance; qreal accumTime; + KisSpacingInformation spacing; qreal spacingUpdateInterval; + // Accumulator of time passed since the last spacing update + qreal timeSinceSpacingUpdate; + + KisTimingInformation timing; + qreal timingUpdateInterval; + // Accumulator of time passed since the last timing update + qreal timeSinceTimingUpdate; + // Information about the last position considered (not necessarily a painted dab) QPointF lastPosition; qreal lastTime; qreal lastAngle; bool lastDabInfoValid; + // Information about the last painted dab KisPaintInformation lastPaintInformation; bool lastPaintInfoValid; qreal lockedDrawingAngle; bool hasLockedDrawingAngle; qreal totalDistance; }; +struct Q_DECL_HIDDEN KisDistanceInitInfo::Private { + Private() : + hasLastInfo(false), + lastPosition(), + lastTime(0.0), + lastAngle(0.0), + spacingUpdateInterval(LONG_TIME), + timingUpdateInterval(LONG_TIME) {} + + + // Indicates whether lastPosition, lastTime, and lastAngle are valid or not. + bool hasLastInfo; + + QPointF lastPosition; + qreal lastTime; + qreal lastAngle; + + qreal spacingUpdateInterval; + qreal timingUpdateInterval; +}; + KisDistanceInitInfo::KisDistanceInitInfo() - : m_hasLastInfo(false) - , m_lastPosition() - , m_lastTime(0.0) - , m_lastAngle(0.0) - , m_spacingUpdateInterval(LONG_TIME) + : m_d(new Private) { } -KisDistanceInitInfo::KisDistanceInitInfo(qreal spacingUpdateInterval) - : m_hasLastInfo(false) - , m_lastPosition() - , m_lastTime(0.0) - , m_lastAngle(0.0) - , m_spacingUpdateInterval(spacingUpdateInterval) +KisDistanceInitInfo::KisDistanceInitInfo(qreal spacingUpdateInterval, qreal timingUpdateInterval) + : m_d(new Private) { + m_d->spacingUpdateInterval = spacingUpdateInterval; + m_d->timingUpdateInterval = timingUpdateInterval; } KisDistanceInitInfo::KisDistanceInitInfo(const QPointF &lastPosition, qreal lastTime, qreal lastAngle) - : m_hasLastInfo(true) - , m_lastPosition(lastPosition) - , m_lastTime(lastTime) - , m_lastAngle(lastAngle) - , m_spacingUpdateInterval(LONG_TIME) + : m_d(new Private) { + m_d->hasLastInfo = true; + m_d->lastPosition = lastPosition; + m_d->lastTime = lastTime; + m_d->lastAngle = lastAngle; } KisDistanceInitInfo::KisDistanceInitInfo(const QPointF &lastPosition, qreal lastTime, - qreal lastAngle, qreal spacingUpdateInterval) - : m_hasLastInfo(true) - , m_lastPosition(lastPosition) - , m_lastTime(lastTime) - , m_lastAngle(lastAngle) - , m_spacingUpdateInterval(spacingUpdateInterval) + qreal lastAngle, qreal spacingUpdateInterval, + qreal timingUpdateInterval) + : m_d(new Private) { + m_d->hasLastInfo = true; + m_d->lastPosition = lastPosition; + m_d->lastTime = lastTime; + m_d->lastAngle = lastAngle; + m_d->spacingUpdateInterval = spacingUpdateInterval; + m_d->timingUpdateInterval = timingUpdateInterval; +} + +KisDistanceInitInfo::KisDistanceInitInfo(const KisDistanceInitInfo &rhs) + : m_d(new Private(*rhs.m_d)) +{ +} + +KisDistanceInitInfo::~KisDistanceInitInfo() +{ + delete m_d; } bool KisDistanceInitInfo::operator==(const KisDistanceInitInfo &other) const { - if (m_spacingUpdateInterval != other.m_spacingUpdateInterval - || m_hasLastInfo != other.m_hasLastInfo) + if (m_d->spacingUpdateInterval != other.m_d->spacingUpdateInterval + || m_d->timingUpdateInterval != other.m_d->timingUpdateInterval + || m_d->hasLastInfo != other.m_d->hasLastInfo) { return false; } - if (m_hasLastInfo) { - if (m_lastPosition != other.m_lastPosition || m_lastTime != other.m_lastTime - || m_lastAngle != other.m_lastAngle) + if (m_d->hasLastInfo) { + if (m_d->lastPosition != other.m_d->lastPosition || m_d->lastTime != other.m_d->lastTime + || m_d->lastAngle != other.m_d->lastAngle) { return false; } } return true; } bool KisDistanceInitInfo::operator!=(const KisDistanceInitInfo &other) const { return !(*this == other); } +KisDistanceInitInfo &KisDistanceInitInfo::operator=(const KisDistanceInitInfo &rhs) +{ + *m_d = *rhs.m_d; + return *this; +} + KisDistanceInformation KisDistanceInitInfo::makeDistInfo() { - if (m_hasLastInfo) { - return KisDistanceInformation(m_lastPosition, m_lastTime, m_lastAngle, - m_spacingUpdateInterval); + if (m_d->hasLastInfo) { + return KisDistanceInformation(m_d->lastPosition, m_d->lastTime, m_d->lastAngle, + m_d->spacingUpdateInterval, m_d->timingUpdateInterval); } else { - return KisDistanceInformation(m_spacingUpdateInterval); + return KisDistanceInformation(m_d->spacingUpdateInterval, m_d->timingUpdateInterval); } } void KisDistanceInitInfo::toXML(QDomDocument &doc, QDomElement &elt) const { - elt.setAttribute("spacingUpdateInterval", QString::number(m_spacingUpdateInterval, 'g', 15)); - if (m_hasLastInfo) { + elt.setAttribute("spacingUpdateInterval", QString::number(m_d->spacingUpdateInterval, 'g', 15)); + elt.setAttribute("timingUpdateInterval", QString::number(m_d->timingUpdateInterval, 'g', 15)); + if (m_d->hasLastInfo) { QDomElement lastInfoElt = doc.createElement("LastInfo"); - lastInfoElt.setAttribute("lastPosX", QString::number(m_lastPosition.x(), 'g', 15)); - lastInfoElt.setAttribute("lastPosY", QString::number(m_lastPosition.y(), 'g', 15)); - lastInfoElt.setAttribute("lastTime", QString::number(m_lastTime, 'g', 15)); - lastInfoElt.setAttribute("lastAngle", QString::number(m_lastAngle, 'g', 15)); + lastInfoElt.setAttribute("lastPosX", QString::number(m_d->lastPosition.x(), 'g', 15)); + lastInfoElt.setAttribute("lastPosY", QString::number(m_d->lastPosition.y(), 'g', 15)); + lastInfoElt.setAttribute("lastTime", QString::number(m_d->lastTime, 'g', 15)); + lastInfoElt.setAttribute("lastAngle", QString::number(m_d->lastAngle, 'g', 15)); elt.appendChild(lastInfoElt); } } KisDistanceInitInfo KisDistanceInitInfo::fromXML(const QDomElement &elt) { const qreal spacingUpdateInterval = qreal(KisDomUtils::toDouble(elt.attribute("spacingUpdateInterval", QString::number(LONG_TIME, 'g', 15)))); + const qreal timingUpdateInterval = qreal(KisDomUtils::toDouble(elt.attribute("timingUpdateInterval", + QString::number(LONG_TIME, 'g', 15)))); const QDomElement lastInfoElt = elt.firstChildElement("LastInfo"); const bool hasLastInfo = !lastInfoElt.isNull(); if (hasLastInfo) { const qreal lastPosX = qreal(KisDomUtils::toDouble(lastInfoElt.attribute("lastPosX", "0.0"))); const qreal lastPosY = qreal(KisDomUtils::toDouble(lastInfoElt.attribute("lastPosY", "0.0"))); const qreal lastTime = qreal(KisDomUtils::toDouble(lastInfoElt.attribute("lastTime", "0.0"))); const qreal lastAngle = qreal(KisDomUtils::toDouble(lastInfoElt.attribute("lastAngle", "0.0"))); return KisDistanceInitInfo(QPointF(lastPosX, lastPosY), lastTime, lastAngle, - spacingUpdateInterval); + spacingUpdateInterval, timingUpdateInterval); } else { - return KisDistanceInitInfo(spacingUpdateInterval); + return KisDistanceInitInfo(spacingUpdateInterval, timingUpdateInterval); } } KisDistanceInformation::KisDistanceInformation() : m_d(new Private) { } -KisDistanceInformation::KisDistanceInformation(qreal spacingUpdateInterval) +KisDistanceInformation::KisDistanceInformation(qreal spacingUpdateInterval, + qreal timingUpdateInterval) : m_d(new Private) { m_d->spacingUpdateInterval = spacingUpdateInterval; + m_d->timingUpdateInterval = timingUpdateInterval; } KisDistanceInformation::KisDistanceInformation(const QPointF &lastPosition, qreal lastTime, qreal lastAngle) : m_d(new Private) { m_d->lastPosition = lastPosition; m_d->lastTime = lastTime; m_d->lastAngle = lastAngle; m_d->lastDabInfoValid = true; } KisDistanceInformation::KisDistanceInformation(const QPointF &lastPosition, qreal lastTime, qreal lastAngle, - qreal spacingUpdateInterval) + qreal spacingUpdateInterval, + qreal timingUpdateInterval) : KisDistanceInformation(lastPosition, lastTime, lastAngle) { m_d->spacingUpdateInterval = spacingUpdateInterval; + m_d->timingUpdateInterval = timingUpdateInterval; } KisDistanceInformation::KisDistanceInformation(const KisDistanceInformation &rhs) : m_d(new Private(*rhs.m_d)) { } KisDistanceInformation::KisDistanceInformation(const KisDistanceInformation &rhs, int levelOfDetail) : m_d(new Private(*rhs.m_d)) { KIS_ASSERT_RECOVER_NOOP(!m_d->lastPaintInfoValid && "The distance information " "should be cloned before the " "actual painting is started"); KisLodTransform t(levelOfDetail); m_d->lastPosition = t.map(m_d->lastPosition); } KisDistanceInformation& KisDistanceInformation::operator=(const KisDistanceInformation &rhs) { *m_d = *rhs.m_d; return *this; } void KisDistanceInformation::overrideLastValues(const QPointF &lastPosition, qreal lastTime, qreal lastAngle) { m_d->lastPosition = lastPosition; m_d->lastTime = lastTime; m_d->lastAngle = lastAngle; m_d->lastDabInfoValid = true; } KisDistanceInformation::~KisDistanceInformation() { delete m_d; } const KisSpacingInformation& KisDistanceInformation::currentSpacing() const { return m_d->spacing; } -void KisDistanceInformation::setSpacing(const KisSpacingInformation &spacing) +void KisDistanceInformation::updateSpacing(const KisSpacingInformation &spacing) { m_d->spacing = spacing; + m_d->timeSinceSpacingUpdate = 0.0; } bool KisDistanceInformation::needsSpacingUpdate() const { - // Only require spacing updates between dabs if timed spacing is enabled. - return m_d->spacing.isTimedSpacingEnabled() && m_d->accumTime >= m_d->spacingUpdateInterval; + return m_d->timeSinceSpacingUpdate >= m_d->spacingUpdateInterval; +} + +const KisTimingInformation &KisDistanceInformation::currentTiming() const +{ + return m_d->timing; +} + +void KisDistanceInformation::updateTiming(const KisTimingInformation &timing) +{ + m_d->timing = timing; + m_d->timeSinceTimingUpdate = 0.0; +} + +bool KisDistanceInformation::needsTimingUpdate() const +{ + return m_d->timeSinceTimingUpdate >= m_d->timingUpdateInterval; } bool KisDistanceInformation::hasLastDabInformation() const { return m_d->lastDabInfoValid; } QPointF KisDistanceInformation::lastPosition() const { return m_d->lastPosition; } qreal KisDistanceInformation::lastTime() const { return m_d->lastTime; } qreal KisDistanceInformation::lastDrawingAngle() const { return m_d->lastAngle; } bool KisDistanceInformation::hasLastPaintInformation() const { return m_d->lastPaintInfoValid; } const KisPaintInformation& KisDistanceInformation::lastPaintInformation() const { return m_d->lastPaintInformation; } bool KisDistanceInformation::isStarted() const { return m_d->lastPaintInfoValid; } void KisDistanceInformation::registerPaintedDab(const KisPaintInformation &info, - const KisSpacingInformation &spacing) + const KisSpacingInformation &spacing, + const KisTimingInformation &timing) { m_d->totalDistance += KisAlgebra2D::norm(info.pos() - m_d->lastPosition); m_d->lastPaintInformation = info; m_d->lastPaintInfoValid = true; m_d->lastAngle = nextDrawingAngle(info.pos()); m_d->lastPosition = info.pos(); m_d->lastTime = info.currentTime(); m_d->lastDabInfoValid = true; m_d->spacing = spacing; + m_d->timing = timing; } qreal KisDistanceInformation::getNextPointPosition(const QPointF &start, const QPointF &end, qreal startTime, qreal endTime) { // Compute interpolation factor based on distance. qreal distanceFactor = -1.0; if (m_d->spacing.isDistanceSpacingEnabled()) { distanceFactor = m_d->spacing.isIsotropic() ? getNextPointPositionIsotropic(start, end) : getNextPointPositionAnisotropic(start, end); } // Compute interpolation factor based on time. qreal timeFactor = -1.0; - if (m_d->spacing.isTimedSpacingEnabled()) { + if (m_d->timing.isTimedSpacingEnabled()) { timeFactor = getNextPointPositionTimed(startTime, endTime); } // Return the distance-based or time-based factor, whichever is smallest. + qreal t = -1.0; if (distanceFactor < 0.0) { - return timeFactor; + t = timeFactor; } else if (timeFactor < 0.0) { - return distanceFactor; + t = distanceFactor; } else { - return qMin(distanceFactor, timeFactor); + t = qMin(distanceFactor, timeFactor); } + + // If we aren't ready to paint a dab, accumulate time for the spacing/timing updates that might + // be needed between dabs. + if (t < 0.0) { + m_d->timeSinceSpacingUpdate += endTime - startTime; + m_d->timeSinceTimingUpdate += endTime - startTime; + } + + // If we are ready to paint a dab, reset the accumulated time for spacing/timing updates. + else { + m_d->timeSinceSpacingUpdate = 0.0; + m_d->timeSinceTimingUpdate = 0.0; + } + + return t; } qreal KisDistanceInformation::getNextPointPositionIsotropic(const QPointF &start, const QPointF &end) { qreal distance = m_d->accumDistance.x(); qreal spacing = qMax(MIN_DISTANCE_SPACING, m_d->spacing.distanceSpacing().x()); if (start == end) { return -1; } qreal dragVecLength = QVector2D(end - start).length(); qreal nextPointDistance = spacing - distance; qreal t; // nextPointDistance can sometimes be negative if the spacing info has been modified since the // last interpolation attempt. In that case, have a point painted immediately. if (nextPointDistance <= 0.0) { resetAccumulators(); t = 0.0; } else if (nextPointDistance <= dragVecLength) { t = nextPointDistance / dragVecLength; resetAccumulators(); } else { t = -1; m_d->accumDistance.rx() += dragVecLength; } return t; } qreal KisDistanceInformation::getNextPointPositionAnisotropic(const QPointF &start, const QPointF &end) { if (start == end) { return -1; } qreal a_rev = 1.0 / qMax(MIN_DISTANCE_SPACING, m_d->spacing.distanceSpacing().x()); qreal b_rev = 1.0 / qMax(MIN_DISTANCE_SPACING, m_d->spacing.distanceSpacing().y()); qreal x = m_d->accumDistance.x(); qreal y = m_d->accumDistance.y(); qreal gamma = pow2(x * a_rev) + pow2(y * b_rev) - 1; // If the distance accumulator is already past the spacing ellipse, have a point painted // immediately. This can happen if the spacing info has been modified since the last // interpolation attempt. if (gamma >= 0.0) { resetAccumulators(); return 0.0; } static const qreal eps = 2e-3; // < 0.2 deg qreal currentRotation = m_d->spacing.rotation(); if (m_d->spacing.coordinateSystemFlipped()) { currentRotation = 2 * M_PI - currentRotation; } QPointF diff = end - start; if (currentRotation > eps) { QTransform rot; // since the ellipse is symmetrical, the sign // of rotation doesn't matter rot.rotateRadians(currentRotation); diff = rot.map(diff); } qreal dx = qAbs(diff.x()); qreal dy = qAbs(diff.y()); qreal alpha = pow2(dx * a_rev) + pow2(dy * b_rev); qreal beta = x * dx * a_rev * a_rev + y * dy * b_rev * b_rev; qreal D_4 = pow2(beta) - alpha * gamma; qreal t = -1.0; if (D_4 >= 0) { qreal k = (-beta + qSqrt(D_4)) / alpha; if (k >= 0.0 && k <= 1.0) { t = k; resetAccumulators(); } else { m_d->accumDistance += KisAlgebra2D::abs(diff); } } else { warnKrita << "BUG: No solution for elliptical spacing equation has been found. This shouldn't have happened."; } return t; } qreal KisDistanceInformation::getNextPointPositionTimed(qreal startTime, qreal endTime) { // If start time is not before end time, do not interpolate. if (!(startTime < endTime)) { return -1.0; } - qreal timedSpacingInterval = qBound(MIN_TIMED_INTERVAL, m_d->spacing.timedSpacingInterval(), + qreal timedSpacingInterval = qBound(MIN_TIMED_INTERVAL, m_d->timing.timedSpacingInterval(), MAX_TIMED_INTERVAL); qreal nextPointInterval = timedSpacingInterval - m_d->accumTime; qreal t = -1.0; // nextPointInterval can sometimes be negative if the spacing info has been modified since the // last interpolation attempt. In that case, have a point painted immediately. if (nextPointInterval <= 0.0) { resetAccumulators(); t = 0.0; } else if (nextPointInterval <= endTime - startTime) { resetAccumulators(); t = nextPointInterval / (endTime - startTime); } else { m_d->accumTime += endTime - startTime; t = -1.0; } return t; } void KisDistanceInformation::resetAccumulators() { m_d->accumDistance = QPointF(); m_d->accumTime = 0.0; } bool KisDistanceInformation::hasLockedDrawingAngle() const { return m_d->hasLockedDrawingAngle; } qreal KisDistanceInformation::lockedDrawingAngle() const { return m_d->lockedDrawingAngle; } void KisDistanceInformation::setLockedDrawingAngle(qreal angle) { m_d->hasLockedDrawingAngle = true; m_d->lockedDrawingAngle = angle; } qreal KisDistanceInformation::nextDrawingAngle(const QPointF &nextPos, bool considerLockedAngle) const { if (!m_d->lastDabInfoValid) { warnKrita << "KisDistanceInformation::nextDrawingAngle()" << "No last dab data"; return 0.0; } // Compute the drawing angle. If the new position is the same as the previous position, an angle // can't be computed. In that case, act as if the angle is the same as in the previous dab. return drawingAngleImpl(m_d->lastPosition, nextPos, considerLockedAngle, m_d->lastAngle); } QPointF KisDistanceInformation::nextDrawingDirectionVector(const QPointF &nextPos, bool considerLockedAngle) const { if (!m_d->lastDabInfoValid) { warnKrita << "KisDistanceInformation::nextDrawingDirectionVector()" << "No last dab data"; return QPointF(1.0, 0.0); } // Compute the direction vector. If the new position is the same as the previous position, a // direction can't be computed. In that case, act as if the direction is the same as in the // previous dab. return drawingDirectionVectorImpl(m_d->lastPosition, nextPos, considerLockedAngle, m_d->lastAngle); } qreal KisDistanceInformation::scalarDistanceApprox() const { return m_d->totalDistance; } qreal KisDistanceInformation::drawingAngleImpl(const QPointF &start, const QPointF &end, bool considerLockedAngle, qreal defaultAngle) const { if (m_d->hasLockedDrawingAngle && considerLockedAngle) { return m_d->lockedDrawingAngle; } // If the start and end positions are the same, we can't compute an angle. In that case, use the // provided default. return KisAlgebra2D::directionBetweenPoints(start, end, defaultAngle); } QPointF KisDistanceInformation::drawingDirectionVectorImpl(const QPointF &start, const QPointF &end, bool considerLockedAngle, qreal defaultAngle) const { if (m_d->hasLockedDrawingAngle && considerLockedAngle) { return QPointF(cos(m_d->lockedDrawingAngle), sin(m_d->lockedDrawingAngle)); } // If the start and end positions are the same, we can't compute a drawing direction. In that // case, use the provided default. if (KisAlgebra2D::fuzzyPointCompare(start, end)) { return QPointF(cos(defaultAngle), sin(defaultAngle)); } const QPointF diff(end - start); return KisAlgebra2D::normalize(diff); } diff --git a/libs/image/kis_distance_information.h b/libs/image/kis_distance_information.h index 9a64340f86..5c8462e2a4 100644 --- a/libs/image/kis_distance_information.h +++ b/libs/image/kis_distance_information.h @@ -1,335 +1,203 @@ /* * Copyright (c) 2010 Cyrille Berger * Copyright (c) 2013 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef _KIS_DISTANCE_INFORMATION_H_ #define _KIS_DISTANCE_INFORMATION_H_ #include #include #include #include #include "kritaimage_export.h" class KisPaintInformation; +class KisSpacingInformation; +class KisTimingInformation; class KisDistanceInformation; -/** - * A time in milliseconds that is assumed to be longer than any stroke (or other paint operation) - * will ever last. This is used instead of infinity to avoid potential errors. The value is - * approximately ten years. - */ -const qreal LONG_TIME = 320000000000.0; - -/** - * This structure contains information about the desired spacing - * requested by the paintAt call - */ -class KisSpacingInformation { -public: - explicit KisSpacingInformation() - : m_distanceSpacingEnabled(true) - , m_distanceSpacing(0.0, 0.0) - , m_timedSpacingEnabled(false) - , m_timedSpacingInterval(0.0) - , m_rotation(0.0) - , m_coordinateSystemFlipped(false) - { - } - - explicit KisSpacingInformation(qreal isotropicSpacing) - : m_distanceSpacingEnabled(true) - , m_distanceSpacing(isotropicSpacing, isotropicSpacing) - , m_timedSpacingEnabled(false) - , m_timedSpacingInterval(0.0) - , m_rotation(0.0) - , m_coordinateSystemFlipped(false) - { - } - - explicit KisSpacingInformation(const QPointF &anisotropicSpacing, qreal rotation, bool coordinateSystemFlipped) - : m_distanceSpacingEnabled(true) - , m_distanceSpacing(anisotropicSpacing) - , m_timedSpacingEnabled(false) - , m_timedSpacingInterval(0.0) - , m_rotation(rotation) - , m_coordinateSystemFlipped(coordinateSystemFlipped) - { - } - - explicit KisSpacingInformation(qreal isotropicSpacing, - qreal timedSpacingInterval) - : m_distanceSpacingEnabled(true) - , m_distanceSpacing(isotropicSpacing, isotropicSpacing) - , m_timedSpacingEnabled(true) - , m_timedSpacingInterval(timedSpacingInterval) - , m_rotation(0.0) - , m_coordinateSystemFlipped(false) - { - } - - explicit KisSpacingInformation(const QPointF &anisotropicSpacing, - qreal rotation, - bool coordinateSystemFlipped, - qreal timedSpacingInterval) - : m_distanceSpacingEnabled(true) - , m_distanceSpacing(anisotropicSpacing) - , m_timedSpacingEnabled(true) - , m_timedSpacingInterval(timedSpacingInterval) - , m_rotation(rotation) - , m_coordinateSystemFlipped(coordinateSystemFlipped) - { - } - - explicit KisSpacingInformation(bool distanceSpacingEnabled, - qreal isotropicSpacing, - bool timedSpacingEnabled, - qreal timedSpacingInterval) - : m_distanceSpacingEnabled(distanceSpacingEnabled) - , m_distanceSpacing(isotropicSpacing, isotropicSpacing) - , m_timedSpacingEnabled(timedSpacingEnabled) - , m_timedSpacingInterval(timedSpacingInterval) - , m_rotation(0.0) - , m_coordinateSystemFlipped(false) - { - } - - explicit KisSpacingInformation(bool distanceSpacingEnabled, - const QPointF &anisotropicSpacing, - qreal rotation, - bool coordinateSystemFlipped, - bool timedSpacingEnabled, - qreal timedSpacingInterval) - : m_distanceSpacingEnabled(distanceSpacingEnabled) - , m_distanceSpacing(anisotropicSpacing) - , m_timedSpacingEnabled(timedSpacingEnabled) - , m_timedSpacingInterval(timedSpacingInterval) - , m_rotation(rotation) - , m_coordinateSystemFlipped(coordinateSystemFlipped) - { - } - - /** - * @return True if and only if distance-based spacing is enabled. - */ - inline bool isDistanceSpacingEnabled() const { - return m_distanceSpacingEnabled; - } - - inline QPointF distanceSpacing() const { - return m_distanceSpacing; - } - - /** - * @return True if and only if time-based spacing is enabled. - */ - inline bool isTimedSpacingEnabled() const { - return m_timedSpacingEnabled; - } - - /** - * @return The desired maximum amount of time between dabs, in milliseconds. Returns LONG_TIME - * if time-based spacing is disabled. - */ - inline qreal timedSpacingInterval() const { - return isTimedSpacingEnabled() ? - m_timedSpacingInterval : - LONG_TIME; - } - - inline bool isIsotropic() const { - return m_distanceSpacing.x() == m_distanceSpacing.y(); - } - - inline qreal scalarApprox() const { - return isIsotropic() ? m_distanceSpacing.x() : QVector2D(m_distanceSpacing).length(); - } - - inline qreal rotation() const { - return m_rotation; - } - - bool coordinateSystemFlipped() const { - return m_coordinateSystemFlipped; - } - -private: - - // Distance-based spacing - bool m_distanceSpacingEnabled; - QPointF m_distanceSpacing; - - // Time-based spacing (interval is in milliseconds) - bool m_timedSpacingEnabled; - qreal m_timedSpacingInterval; - - qreal m_rotation; - bool m_coordinateSystemFlipped; -}; - /** * Represents some information that can be used to initialize a KisDistanceInformation object. The * main purpose of this class is to allow serialization of KisDistanceInformation initial settings * to XML. */ class KRITAIMAGE_EXPORT KisDistanceInitInfo { public: /** - * Creates a KisDistanceInitInfo with no initial last dab information, and spacing update - * interval set to LONG_TIME. + * Creates a KisDistanceInitInfo with no initial last dab information, and spacing and timing + * update intervals set to LONG_TIME. */ explicit KisDistanceInitInfo(); /** * Creates a KisDistanceInitInfo with no initial last dab information, and the specified spacing - * update interval. + * and timing update intervals. */ - explicit KisDistanceInitInfo(qreal spacingUpdateInterval); + explicit KisDistanceInitInfo(qreal spacingUpdateInterval, qreal timingUpdateInterval); /** - * Creates a KisDistanceInitInfo with the specified last dab information, and spacing update - * interval set to LONG_TIME. + * Creates a KisDistanceInitInfo with the specified last dab information, and spacing and timing + * update intervals set to LONG_TIME. */ explicit KisDistanceInitInfo(const QPointF &lastPosition, qreal lastTime, qreal lastAngle); /** - * Creates a KisDistanceInitInfo with the specified last dab information and spacing update - * interval. + * Creates a KisDistanceInitInfo with the specified last dab information and spacing and timing + * update intervals. */ explicit KisDistanceInitInfo(const QPointF &lastPosition, qreal lastTime, qreal lastAngle, - qreal spacingUpdateInterval); + qreal spacingUpdateInterval, qreal timingUpdateInterval); + + KisDistanceInitInfo(const KisDistanceInitInfo &rhs); + + ~KisDistanceInitInfo(); bool operator==(const KisDistanceInitInfo &other) const; bool operator!=(const KisDistanceInitInfo &other) const; + KisDistanceInitInfo &operator=(const KisDistanceInitInfo &rhs); + /** * Constructs a KisDistanceInformation with initial settings based on this object. */ KisDistanceInformation makeDistInfo(); void toXML(QDomDocument &doc, QDomElement &elt) const; static KisDistanceInitInfo fromXML(const QDomElement &elt); private: - // Indicates whether lastPosition, lastTime, and lastAngle are valid or not. - bool m_hasLastInfo; - - QPointF m_lastPosition; - qreal m_lastTime; - qreal m_lastAngle; - - qreal m_spacingUpdateInterval; + struct Private; + Private * const m_d; }; /** - * This structure is used as return value of paintLine to contain - * information that is needed to be passed for the next call. + * This structure keeps track of distance and timing information during a stroke, e.g. the time or + * distance moved since the last dab. */ class KRITAIMAGE_EXPORT KisDistanceInformation { public: KisDistanceInformation(); - KisDistanceInformation(qreal spacingUpdateInterval); + KisDistanceInformation(qreal spacingUpdateInterval, qreal timingUpdateInterval); KisDistanceInformation(const QPointF &lastPosition, qreal lastTime, qreal lastAngle); /** * @param spacingUpdateInterval The amount of time allowed between spacing updates, in - * milliseconds. Only used when timed spacing is enabled. + * milliseconds. Use LONG_TIME to only allow spacing updates when a + * dab is painted. + * @param timingUpdateInterval The amount of time allowed between time-based spacing updates, in + * milliseconds. Use LONG_TIME to only allow timing updates when a + * dab is painted. */ KisDistanceInformation(const QPointF &lastPosition, qreal lastTime, qreal lastAngle, - qreal spacingUpdateInterval); + qreal spacingUpdateInterval, qreal timingUpdateInterval); KisDistanceInformation(const KisDistanceInformation &rhs); KisDistanceInformation(const KisDistanceInformation &rhs, int levelOfDetail); KisDistanceInformation& operator=(const KisDistanceInformation &rhs); ~KisDistanceInformation(); const KisSpacingInformation& currentSpacing() const; - void setSpacing(const KisSpacingInformation &spacing); + void updateSpacing(const KisSpacingInformation &spacing); /** * Returns true if this KisDistanceInformation should have its spacing information updated * immediately (regardless of whether a dab is ready to be painted). */ bool needsSpacingUpdate() const; + const KisTimingInformation ¤tTiming() const; + void updateTiming(const KisTimingInformation &timing); + /** + * Returns true if this KisDistanceInformation should have its timing information updated + * immediately (regardless of whether a dab is ready to be painted). + */ + bool needsTimingUpdate() const; + bool hasLastDabInformation() const; QPointF lastPosition() const; qreal lastTime() const; qreal lastDrawingAngle() const; bool hasLastPaintInformation() const; const KisPaintInformation& lastPaintInformation() const; + /** + * @param spacing The new effective spacing after the dab. (Painting a dab is always supposed to + * cause a spacing update.) + * @param timing The new effective timing after the dab. (Painting a dab is always supposed to + * cause a timing update.) + */ void registerPaintedDab(const KisPaintInformation &info, - const KisSpacingInformation &spacing); + const KisSpacingInformation &spacing, + const KisTimingInformation &timing); qreal getNextPointPosition(const QPointF &start, const QPointF &end, qreal startTime, qreal endTime); /** * \return true if at least one dab has been painted with this * distance information */ bool isStarted() const; bool hasLockedDrawingAngle() const; qreal lockedDrawingAngle() const; void setLockedDrawingAngle(qreal angle); /** * Computes the next drawing angle assuming that the next painting position will be nextPos. * This method should not be called when hasLastDabInformation() is false. */ qreal nextDrawingAngle(const QPointF &nextPos, bool considerLockedAngle = true) const; /** * Returns a unit vector pointing in the direction that would have been indicated by a call to * nextDrawingAngle. This method should not be called when hasLastDabInformation() is false. */ QPointF nextDrawingDirectionVector(const QPointF &nextPos, bool considerLockedAngle = true) const; qreal scalarDistanceApprox() const; void overrideLastValues(const QPointF &lastPosition, qreal lastTime, qreal lastAngle); private: qreal getNextPointPositionIsotropic(const QPointF &start, const QPointF &end); qreal getNextPointPositionAnisotropic(const QPointF &start, const QPointF &end); qreal getNextPointPositionTimed(qreal startTime, qreal endTime); void resetAccumulators(); qreal drawingAngleImpl(const QPointF &start, const QPointF &end, bool considerLockedAngle = true, qreal defaultAngle = 0.0) const; QPointF drawingDirectionVectorImpl(const QPointF &start, const QPointF &end, bool considerLockedAngle = true, qreal defaultAngle = 0.0) const; private: struct Private; Private * const m_d; }; #endif diff --git a/libs/image/kis_spacing_information.h b/libs/image/kis_spacing_information.h new file mode 100644 index 0000000000..48dd3baa5a --- /dev/null +++ b/libs/image/kis_spacing_information.h @@ -0,0 +1,94 @@ +#ifndef KIS_SPACING_INFORMATION_H +#define KIS_SPACING_INFORMATION_H + +#include "kritaimage_export.h" + +/** + * Contains information about distance-based spacing settings in a stroke. The spacing settings may + * be different at different parts of a stroke, e.g. if spacing is linked to pressure; a + * KisSpacingInformation represents the effective spacing at a single specific part of the stroke. + */ +class KRITAIMAGE_EXPORT KisSpacingInformation { +public: + explicit KisSpacingInformation() + : m_distanceSpacingEnabled(true) + , m_distanceSpacing(0.0, 0.0) + , m_rotation(0.0) + , m_coordinateSystemFlipped(false) + { + } + + explicit KisSpacingInformation(qreal isotropicSpacing) + : m_distanceSpacingEnabled(true) + , m_distanceSpacing(isotropicSpacing, isotropicSpacing) + , m_rotation(0.0) + , m_coordinateSystemFlipped(false) + { + } + + explicit KisSpacingInformation(const QPointF &anisotropicSpacing, qreal rotation, + bool coordinateSystemFlipped) + : m_distanceSpacingEnabled(true) + , m_distanceSpacing(anisotropicSpacing) + , m_rotation(rotation) + , m_coordinateSystemFlipped(coordinateSystemFlipped) + { + } + + explicit KisSpacingInformation(bool distanceSpacingEnabled, qreal isotropicSpacing) + : m_distanceSpacingEnabled(distanceSpacingEnabled) + , m_distanceSpacing(isotropicSpacing, isotropicSpacing) + , m_rotation(0.0) + , m_coordinateSystemFlipped(false) + { + } + + explicit KisSpacingInformation(bool distanceSpacingEnabled, + const QPointF &anisotropicSpacing, + qreal rotation, + bool coordinateSystemFlipped) + : m_distanceSpacingEnabled(distanceSpacingEnabled) + , m_distanceSpacing(anisotropicSpacing) + , m_rotation(rotation) + , m_coordinateSystemFlipped(coordinateSystemFlipped) + { + } + + /** + * @return True if and only if distance-based spacing is enabled. + */ + inline bool isDistanceSpacingEnabled() const { + return m_distanceSpacingEnabled; + } + + inline QPointF distanceSpacing() const { + return m_distanceSpacing; + } + + inline bool isIsotropic() const { + return m_distanceSpacing.x() == m_distanceSpacing.y(); + } + + inline qreal scalarApprox() const { + return isIsotropic() ? m_distanceSpacing.x() : QVector2D(m_distanceSpacing).length(); + } + + inline qreal rotation() const { + return m_rotation; + } + + bool coordinateSystemFlipped() const { + return m_coordinateSystemFlipped; + } + +private: + + // Distance-based spacing + bool m_distanceSpacingEnabled; + QPointF m_distanceSpacing; + + qreal m_rotation; + bool m_coordinateSystemFlipped; +}; + +#endif // KIS_SPACING_INFORMATION_H diff --git a/libs/image/kis_timing_information.h b/libs/image/kis_timing_information.h new file mode 100644 index 0000000000..d43698f606 --- /dev/null +++ b/libs/image/kis_timing_information.h @@ -0,0 +1,63 @@ +#ifndef KIS_TIMING_INFORMATION_H +#define KIS_TIMING_INFORMATION_H + +#include "kritaimage_export.h" + +/** + * A time in milliseconds that is assumed to be longer than any stroke (or other paint operation) + * will ever last. This is used instead of infinity to avoid potential errors. The value is + * approximately ten years. + */ +const qreal LONG_TIME = 320000000000.0; + +/** + * Contains information about timing settings in a stroke (mainly for airbrushing effects). The + * timing settings may be different at different parts of a stroke, e.g. if the airbrush rate is + * linked to pressure; a KisTimingInformation represents the effective timing at a single specific + * part of a stroke. + */ +class KRITAIMAGE_EXPORT KisTimingInformation +{ +public: + + /** Makes a KisTimingInformation with timed spacing disabled. */ + explicit KisTimingInformation() + : m_timedSpacingEnabled(false) + , m_timedSpacingInterval(LONG_TIME) + { + } + + /** + * Makes a KisTimingInformation with timed spacing enabled, using the specified interval in + * milliseconds. + */ + explicit KisTimingInformation(qreal interval) + : m_timedSpacingEnabled(true) + , m_timedSpacingInterval(interval) + { + } + + /** + * @return True if and only if time-based spacing is enabled. + */ + inline bool isTimedSpacingEnabled() const { + return m_timedSpacingEnabled; + } + + /** + * @return The desired maximum amount of time between dabs, in milliseconds. Returns LONG_TIME + * if time-based spacing is disabled. + */ + inline qreal timedSpacingInterval() const { + return isTimedSpacingEnabled() ? + m_timedSpacingInterval : + LONG_TIME; + } + +private: + // Time-interval-based spacing + bool m_timedSpacingEnabled; + qreal m_timedSpacingInterval; +}; + +#endif // KIS_TIMING_INFORMATION_H diff --git a/libs/image/tests/kis_distance_information_test.cpp b/libs/image/tests/kis_distance_information_test.cpp index 30fa1d03f9..6f65be1f8c 100644 --- a/libs/image/tests/kis_distance_information_test.cpp +++ b/libs/image/tests/kis_distance_information_test.cpp @@ -1,159 +1,182 @@ #include "kis_distance_information_test.h" #include #include #include #include #include "kis_algebra_2d.h" #include "kis_distance_information.h" +#include "kis_spacing_information.h" +#include "kis_timing_information.h" #include "kis_paint_information.h" void KisDistanceInformationTest::testInitInfo() { // Test equality checking operators. testInitInfoEquality(); // Test XML cloning. testInitInfoXMLClone(); } void KisDistanceInformationTest::testInterpolation() { // Set up a scenario for interpolation. QPointF startPos; QPointF endPos(100.0, -50.0); qreal dist = KisAlgebra2D::norm(endPos - startPos); qreal startTime = 0.0; qreal endTime = 1000.0; qreal interval = endTime - startTime; KisPaintInformation p1(startPos, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, startTime, 0.0); KisPaintInformation p2(endPos, 1.0, 0.0, 0.0, 5.0, 0.0, 1.0, endTime, 0.0); // Test interpolation with various spacing settings. static const qreal interpTolerance = 0.000001; KisDistanceInformation dist1; - dist1.setSpacing(KisSpacingInformation(dist/10.0)); - testInterpolationImpl(p1, p2, dist1, 1.0/10.0, false, interpTolerance); - - KisDistanceInformation dist2(interval/2.0); - dist2.setSpacing(KisSpacingInformation(dist*2.0, interval*1.5)); - testInterpolationImpl(p1, p2, dist2, -1.0, true, interpTolerance); - - KisDistanceInformation dist3(interval*1.1); - dist3.setSpacing(KisSpacingInformation(dist/40.0)); - testInterpolationImpl(p1, p2, dist3, 1.0/40.0, false, interpTolerance); - - KisDistanceInformation dist4; - dist4.setSpacing(KisSpacingInformation(false, 1.0, false, 1.0)); - testInterpolationImpl(p1, p2, dist4, -1.0, false, interpTolerance); - - KisDistanceInformation dist5; - dist5.setSpacing(KisSpacingInformation(false, 1.0, true, interval/20.0)); - testInterpolationImpl(p1, p2, dist5, 1.0/20.0, false, interpTolerance); + dist1.updateSpacing(KisSpacingInformation(dist/10.0)); + dist1.updateTiming(KisTimingInformation()); + testInterpolationImpl(p1, p2, dist1, 1.0/10.0, false, false, interpTolerance); + + KisDistanceInformation dist2(interval/2.0, interval/3.0); + dist2.updateSpacing(KisSpacingInformation(dist*2.0)); + dist2.updateTiming(KisTimingInformation(interval*1.5)); + testInterpolationImpl(p1, p2, dist2, -1.0, true, true, interpTolerance); + + KisDistanceInformation dist3(interval/1.5, interval*2.0); + dist3.updateSpacing(KisSpacingInformation(dist*2.1)); + dist3.updateTiming(KisTimingInformation(interval*1.6)); + testInterpolationImpl(p1, p2, dist3, -1.0, true, false, interpTolerance); + + KisDistanceInformation dist4(interval*1.3, interval/2.4); + dist4.updateSpacing(KisSpacingInformation(dist*1.7)); + dist4.updateTiming(KisTimingInformation(interval*1.9)); + testInterpolationImpl(p1, p2, dist4, -1.0, false, true, interpTolerance); + + KisDistanceInformation dist5(interval*1.1, interval*1.2); + dist5.updateSpacing(KisSpacingInformation(dist/40.0)); + dist5.updateTiming(KisTimingInformation()); + testInterpolationImpl(p1, p2, dist5, 1.0/40.0, false, false, interpTolerance); KisDistanceInformation dist6; - dist6.setSpacing(KisSpacingInformation(true, dist/10.0, true, interval/15.0)); - testInterpolationImpl(p1, p2, dist6, 1.0/15.0, false, interpTolerance); + dist6.updateSpacing(KisSpacingInformation(false, 1.0)); + dist6.updateTiming(KisTimingInformation()); + testInterpolationImpl(p1, p2, dist6, -1.0, false, false, interpTolerance); KisDistanceInformation dist7; - dist7.setSpacing(KisSpacingInformation(true, dist/15.0, true, interval/10.0)); - testInterpolationImpl(p1, p2, dist7, 1.0/15.0, false, interpTolerance); + dist7.updateSpacing(KisSpacingInformation(false, 1.0)); + dist7.updateTiming(KisTimingInformation(interval/20.0)); + testInterpolationImpl(p1, p2, dist7, 1.0/20.0, false, false, interpTolerance); KisDistanceInformation dist8; - dist8.setSpacing(KisSpacingInformation(true, dist * 2.0, true, interval * 1.5)); - testInterpolationImpl(p1, p2, dist8, -1.0, false, interpTolerance); + dist8.updateSpacing(KisSpacingInformation(dist/10.0)); + dist8.updateTiming(KisTimingInformation(interval/15.0)); + testInterpolationImpl(p1, p2, dist8, 1.0/15.0, false, false, interpTolerance); KisDistanceInformation dist9; + dist9.updateSpacing(KisSpacingInformation(dist/15.0)); + dist9.updateTiming(KisTimingInformation(interval/10.0)); + testInterpolationImpl(p1, p2, dist9, 1.0/15.0, false, false, interpTolerance); + + KisDistanceInformation dist10; + dist10.updateSpacing(KisSpacingInformation(dist*2.0)); + dist10.updateTiming(KisTimingInformation(interval*1.5)); + testInterpolationImpl(p1, p2, dist10, -1.0, false, false, interpTolerance); + + KisDistanceInformation dist11; qreal a = 50.0; qreal b = 25.0; - dist9.setSpacing(KisSpacingInformation(QPointF(a * 2.0, b * 2.0), 0.0, false)); + dist11.updateSpacing(KisSpacingInformation(QPointF(a * 2.0, b * 2.0), 0.0, false)); + dist11.updateTiming(KisTimingInformation()); // Compute the expected interpolation factor; we are using anisotropic spacing here. qreal angle = KisAlgebra2D::directionBetweenPoints(startPos, endPos, 0.0); qreal cosTermSqrt = qCos(angle) / a; qreal sinTermSqrt = qSin(angle) / b; qreal spacingDist = 2.0 / qSqrt(cosTermSqrt * cosTermSqrt + sinTermSqrt * sinTermSqrt); qreal expectedInterp = spacingDist / dist; - testInterpolationImpl(p1, p2, dist9, expectedInterp, false, interpTolerance); + testInterpolationImpl(p1, p2, dist11, expectedInterp, false, false, interpTolerance); } void KisDistanceInformationTest::testInitInfoEquality() const { KisDistanceInitInfo info1; KisDistanceInitInfo info2; QVERIFY(info1 == info2); QVERIFY(!(info1 != info2)); - KisDistanceInitInfo info3(0.1); - KisDistanceInitInfo info4(0.1); + KisDistanceInitInfo info3(0.1, 0.5); + KisDistanceInitInfo info4(0.1, 0.5); QVERIFY(info3 == info4); QVERIFY(!(info3 != info4)); KisDistanceInitInfo info5(QPointF(1.1, -10.7), 100.0, 3.3); KisDistanceInitInfo info6(QPointF(1.1, -10.7), 100.0, 3.3); QVERIFY(info5 == info6); QVERIFY(!(info5 != info6)); - KisDistanceInitInfo info7(QPointF(-12.3, 24.0), 104.0, 5.0, 20.1); - KisDistanceInitInfo info8(QPointF(-12.3, 24.0), 104.0, 5.0, 20.1); + KisDistanceInitInfo info7(QPointF(-12.3, 24.0), 104.0, 5.0, 20.1, 35.7); + KisDistanceInitInfo info8(QPointF(-12.3, 24.0), 104.0, 5.0, 20.1, 35.7); QVERIFY(info7 == info8); QVERIFY(!(info7 != info8)); QVERIFY(info1 != info3); QVERIFY(info1 != info5); QVERIFY(info1 != info7); QVERIFY(info3 != info5); QVERIFY(info3 != info7); QVERIFY(info5 != info7); } void KisDistanceInformationTest::testInitInfoXMLClone() const { // Note: Numeric values used here must be values that get serialized to XML exactly (e.g. small // integers). Otherwise, roundoff error in serialization may cause a failure. KisDistanceInitInfo info1; QDomDocument doc; QDomElement elt1 = doc.createElement("Test1"); info1.toXML(doc, elt1); KisDistanceInitInfo clone1 = KisDistanceInitInfo::fromXML(elt1); QVERIFY(clone1 == info1); - KisDistanceInitInfo info2(40.0); + KisDistanceInitInfo info2(40.0, 2.0); QDomElement elt2 = doc.createElement("Test2"); info2.toXML(doc, elt2); KisDistanceInitInfo clone2 = KisDistanceInitInfo::fromXML(elt2); QVERIFY(clone2 == info2); KisDistanceInitInfo info3(QPointF(-8.0, -5.0), 0.0, 60.0); QDomElement elt3 = doc.createElement("Test3"); info3.toXML(doc, elt3); KisDistanceInitInfo clone3 = KisDistanceInitInfo::fromXML(elt3); QVERIFY(clone3 == info3); - KisDistanceInitInfo info4(QPointF(0.0, 9.0), 10.0, 6.0, 1.0); + KisDistanceInitInfo info4(QPointF(0.0, 9.0), 10.0, 6.0, 1.0, 3.0); QDomElement elt4 = doc.createElement("Test4"); info4.toXML(doc, elt4); KisDistanceInitInfo clone4 = KisDistanceInitInfo::fromXML(elt4); QVERIFY(clone4 == info4); } void KisDistanceInformationTest::testInterpolationImpl(const KisPaintInformation &p1, const KisPaintInformation &p2, KisDistanceInformation &dist, qreal interpFactor, bool needSpacingUpdate, + bool needTimingUpdate, qreal interpTolerance) const { qreal actualInterpFactor = dist.getNextPointPosition(p1.pos(), p2.pos(), p1.currentTime(), p2.currentTime()); QVERIFY(qAbs(interpFactor - actualInterpFactor) <= interpTolerance); QCOMPARE(dist.needsSpacingUpdate(), needSpacingUpdate); + QCOMPARE(dist.needsTimingUpdate(), needTimingUpdate); } QTEST_MAIN(KisDistanceInformationTest) diff --git a/libs/image/tests/kis_distance_information_test.h b/libs/image/tests/kis_distance_information_test.h index 12718bb946..6d4c314535 100644 --- a/libs/image/tests/kis_distance_information_test.h +++ b/libs/image/tests/kis_distance_information_test.h @@ -1,32 +1,36 @@ #ifndef KIS_DISTANCE_INFORMATION_TEST_H #define KIS_DISTANCE_INFORMATION_TEST_H #include class KisPaintInformation; class KisDistanceInformation; +/** Tests some functionality in KisDistanceInformation and related classes. */ class KisDistanceInformationTest : public QObject { Q_OBJECT private Q_SLOTS: void testInitInfo(); void testInterpolation(); private: void testInitInfoEquality() const; void testInitInfoXMLClone() const; /** * Performs one interpolation using the specified KisDistanceInformation and checks the results. * @param interpFactor The interpolation factor that the KisDistanceInformation is expected to * return. * @param needSpacingUpdate Indicates whether the KisDistanceInformation is expected to need a * spacing update after the interpolation. + * @param needTimingUpdate Indicates whether the KisDistanceInformation is expected to need a + * timing update after the interpolation. */ void testInterpolationImpl(const KisPaintInformation &p1, const KisPaintInformation &p2, KisDistanceInformation &dist, qreal interpFactor, - bool needSpacingUpdate, qreal interpTolerance) const; + bool needSpacingUpdate, bool needTimingUpdate, + qreal interpTolerance) const; }; #endif // KIS_DISTANCE_INFORMATION_TEST_H diff --git a/libs/image/tests/kis_paintop_test.cpp b/libs/image/tests/kis_paintop_test.cpp index 2111c968fc..f9fd6c788c 100644 --- a/libs/image/tests/kis_paintop_test.cpp +++ b/libs/image/tests/kis_paintop_test.cpp @@ -1,54 +1,55 @@ /* * Copyright (c) 2007 Boudewijn Rempt boud@valdyas.org * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_paintop_test.h" #include #include "kis_paintop.h" #include "kis_painter.h" #include "kis_paint_device.h" +#include "kis_spacing_information.h" class TestPaintOp : public KisPaintOp { public: TestPaintOp(KisPainter * gc) : KisPaintOp(gc) { } protected: KisSpacingInformation paintAt(const KisPaintInformation&) override { return KisSpacingInformation(0.0); } KisSpacingInformation updateSpacingImpl(const KisPaintInformation&) const override { return KisSpacingInformation(0.0); } }; void KisPaintopTest::testCreation() { KisPainter p; TestPaintOp test(&p); } QTEST_MAIN(KisPaintopTest) diff --git a/libs/pigment/resources/KoColorSet.cpp b/libs/pigment/resources/KoColorSet.cpp index 3e845f4716..b239346684 100644 --- a/libs/pigment/resources/KoColorSet.cpp +++ b/libs/pigment/resources/KoColorSet.cpp @@ -1,1559 +1,1559 @@ /* This file is part of the KDE project Copyright (c) 2005 Boudewijn Rempt Copyright (c) 2016 L. E. Segovia This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include // qFromLittleEndian #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "KoColor.h" #include "KoColorProfile.h" #include "KoColorSpaceRegistry.h" #include "KoColorModelStandardIds.h" struct KoColorSet::Private { KoColorSet::PaletteType paletteType; QByteArray data; QString comment; qint32 columns; QVector colors; //ungrouped colors QStringList groupNames; //names of the groups, this is used to determine the order they are in. QMap> groups; //grouped colors. }; KoColorSet::PaletteType detectFormat(const QString &fileName, const QByteArray &ba) { QFileInfo fi(fileName); // .pal if (ba.startsWith("RIFF") && ba.indexOf("PAL data", 8)) { return KoColorSet::RIFF_PAL; } // .gpl else if (ba.startsWith("GIMP Palette")) { return KoColorSet::GPL; } // .pal else if (ba.startsWith("JASC-PAL")) { return KoColorSet::PSP_PAL; } else if (fi.suffix().toLower() == "aco") { return KoColorSet::ACO; } else if (fi.suffix().toLower() == "act") { return KoColorSet::ACT; } else if (fi.suffix().toLower() == "xml") { return KoColorSet::XML; } else if (fi.suffix().toLower() == "kpl") { return KoColorSet::KPL; } else if (fi.suffix().toLower() == "sbz") { return KoColorSet::SBZ; } return KoColorSet::UNKNOWN; } KoColorSet::KoColorSet(const QString& filename) : KoResource(filename) , d(new Private()) { // Implemented in KoResource class d->columns = 0; // Set the default value that the GIMP uses... } KoColorSet::KoColorSet() : KoResource(QString()) , d(new Private()) { d->columns = 0; // Set the default value that the GIMP uses... } /// Create an copied palette KoColorSet::KoColorSet(const KoColorSet& rhs) : QObject(0) , KoResource(QString()) , d(new Private()) { setFilename(rhs.filename()); d->comment = rhs.d->comment; d->columns = rhs.d->columns; d->colors = rhs.d->colors; d->groupNames = rhs.d->groupNames; d->groups = rhs.d->groups; setValid(true); } KoColorSet::~KoColorSet() { } bool KoColorSet::load() { QFile file(filename()); if (file.size() == 0) return false; if (!file.open(QIODevice::ReadOnly)) { warnPigment << "Can't open file " << filename(); return false; } bool res = loadFromDevice(&file); file.close(); return res; } bool KoColorSet::loadFromDevice(QIODevice *dev) { if (!dev->isOpen()) dev->open(QIODevice::ReadOnly); d->data = dev->readAll(); Q_ASSERT(d->data.size() != 0); return init(); } bool KoColorSet::save() { QFile file(filename()); if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) { return false; } saveToDevice(&file); file.close(); return true; } bool KoColorSet::saveToDevice(QIODevice *dev) const { bool res; switch(d->paletteType) { case GPL: res = saveGpl(dev); break; default: res = saveKpl(dev); } if (res) { KoResource::saveToDevice(dev); } return res; } bool KoColorSet::init() { d->colors.clear(); // just in case this is a reload (eg by KoEditColorSetDialog), d->groups.clear(); d->groupNames.clear(); if (filename().isNull()) { warnPigment << "Cannot load palette" << name() << "there is no filename set"; return false; } if (d->data.isNull()) { QFile file(filename()); if (file.size() == 0) { warnPigment << "Cannot load palette" << name() << "there is no data available"; return false; } file.open(QIODevice::ReadOnly); d->data = file.readAll(); file.close(); } bool res = false; d->paletteType = detectFormat(filename(), d->data); switch(d->paletteType) { case GPL: res = loadGpl(); break; case ACT: res = loadAct(); break; case RIFF_PAL: res = loadRiff(); break; case PSP_PAL: res = loadPsp(); break; case ACO: res = loadAco(); break; case XML: res = loadXml(); break; case KPL: res = loadKpl(); break; case SBZ: res = loadSbz(); break; default: res = false; } setValid(res); if (d->columns == 0) { d->columns = 10; } QImage img(d->columns * 4, (d->colors.size() / d->columns) * 4, QImage::Format_ARGB32); QPainter gc(&img); gc.fillRect(img.rect(), Qt::darkGray); int counter = 0; for(int i = 0; i < d->columns; ++i) { for (int j = 0; j < (d->colors.size() / d->columns); ++j) { if (counter < d->colors.size()) { QColor c = d->colors.at(counter).color.toQColor(); gc.fillRect(i * 4, j * 4, 4, 4, c); counter++; } else { break; } } } setImage(img); // save some memory d->data.clear(); return res; } bool KoColorSet::saveGpl(QIODevice *dev) const { QTextStream stream(dev); stream << "GIMP Palette\nName: " << name() << "\nColumns: " << d->columns << "\n#\n"; for (int i = 0; i < d->colors.size(); i++) { const KoColorSetEntry& entry = d->colors.at(i); QColor c = entry.color.toQColor(); stream << c.red() << " " << c.green() << " " << c.blue() << "\t"; if (entry.name.isEmpty()) stream << "Untitled\n"; else stream << entry.name << "\n"; } return true; } quint32 KoColorSet::nColors() { quint32 total = d->colors.count(); if (!d->groups.empty()) { Q_FOREACH (const QVector &group, d->groups.values()) { total += group.size(); } } return total; } quint32 KoColorSet::nColorsGroup(QString groupName) { if (d->groups.contains(groupName)) { return d->groups.value(groupName).size(); } else if (groupName.isEmpty()){ return d->colors.size(); } else { return 0; } } quint32 KoColorSet::getIndexClosestColor(const KoColor color, bool useGivenColorSpace) { quint32 closestIndex = 0; quint8 highestPercentage = 0; quint8 testPercentage = 0; KoColor compare = color; for (quint32 i=0; idifference(compare.data(), entry.data())); if (testPercentage>highestPercentage) { closestIndex = i; highestPercentage = testPercentage; } } return closestIndex; } QString KoColorSet::closestColorName(const KoColor color, bool useGivenColorSpace) { int i = getIndexClosestColor(color, useGivenColorSpace); - QString name = d->colors.at(i).name; + QString name = getColorGlobal(i).name; return name; } void KoColorSet::add(const KoColorSetEntry & c, QString groupName) { if (d->groups.contains(groupName) || d->groupNames.contains(groupName)) { d->groups[groupName].push_back(c); } else { d->colors.push_back(c); } } quint32 KoColorSet::insertBefore(const KoColorSetEntry &c, qint32 index, const QString &groupName) { quint32 newIndex = index; if (d->groups.contains(groupName)) { d->groups[groupName].insert(index, c); } else if (groupName.isEmpty()){ d->colors.insert(index, c);; } else { warnPigment << "Couldn't find group to insert to"; } return newIndex; } void KoColorSet::removeAt(quint32 index, QString groupName) { if (d->groups.contains(groupName)){ if ((quint32)d->groups.value(groupName).size()>index) { d->groups[groupName].remove(index); } } else { if ((quint32)d->colors.size()>index) { d->colors.remove(index); } } } void KoColorSet::clear() { d->colors.clear(); d->groups.clear(); } KoColorSetEntry KoColorSet::getColorGlobal(quint32 index) { KoColorSetEntry e; quint32 groupIndex = index; QString groupName = findGroupByGlobalIndex(index, &groupIndex); e = getColorGroup(groupIndex, groupName); return e; } KoColorSetEntry KoColorSet::getColorGroup(quint32 index, QString groupName) { KoColorSetEntry e; if (d->groups.contains(groupName) && index<(quint32)d->groups.value(groupName).size()) { e = d->groups.value(groupName).at(index); } else if (groupName == QString() && index<(quint32)d->colors.size()) { e = d->colors.at(index); } else { warnPigment << "Color group "<colors.size()<=*index) { *index -= (quint32)d->colors.size(); if (!d->groups.empty() || !d->groupNames.empty()) { QStringList groupNames = getGroupNames(); Q_FOREACH (QString name, groupNames) { quint32 size = (quint32)d->groups.value(name).size(); if (size<=*index) { *index -= size; } else { groupName = name; return groupName; } } } } return groupName; } QString KoColorSet::findGroupByColorName(const QString &name, quint32 *index) { *index = 0; QString groupName = QString(); for (int i = 0; icolors.size(); i++) { if(d->colors.at(i).name == name) { *index = (quint32)i; return groupName; } } QStringList groupNames = getGroupNames(); Q_FOREACH (QString name, groupNames) { for (int i=0; igroups[name].size(); i++) { if(d->groups[name].at(i).name == name) { *index = (quint32)i; groupName = name; return groupName; } } } return groupName; } QString KoColorSet::findGroupByID(const QString &id, quint32 *index) { *index = 0; QString groupName = QString(); for (int i = 0; icolors.size(); i++) { if(d->colors.at(i).id == id) { *index = (quint32)i; return groupName; } } QStringList groupNames = getGroupNames(); Q_FOREACH (QString name, groupNames) { for (int i=0; igroups[name].size(); i++) { if(d->groups[name].at(i).id == id) { *index = (quint32)i; groupName = name; return groupName; } } } return groupName; } QStringList KoColorSet::getGroupNames() { if (d->groupNames.size()groups.size()) { warnPigment << "mismatch between groups and the groupnames list."; return QStringList(d->groups.keys()); } return d->groupNames; } bool KoColorSet::changeGroupName(QString oldGroupName, QString newGroupName) { if (d->groupNames.contains(oldGroupName)==false) { return false; } QVector dummyList = d->groups.value(oldGroupName); d->groups.remove(oldGroupName); d->groups[newGroupName] = dummyList; //rename the string in the stringlist; int index = d->groupNames.indexOf(oldGroupName); d->groupNames.replace(index, newGroupName); return true; } bool KoColorSet::changeColorSetEntry(KoColorSetEntry entry, QString groupName, quint32 index) { if (index>=nColorsGroup(groupName) || (d->groupNames.contains(groupName)==false && groupName.size()>0)) { return false; } if (groupName==QString()) { d->colors[index] = entry; } else { d->groups[groupName][index] = entry; } return true; } void KoColorSet::setColumnCount(int columns) { d->columns = columns; } int KoColorSet::columnCount() { return d->columns; } QString KoColorSet::comment() { return d->comment; } void KoColorSet::setComment(QString comment) { d->comment = comment; } bool KoColorSet::addGroup(const QString &groupName) { if (d->groups.contains(groupName) || d->groupNames.contains(groupName)) { return false; } d->groupNames.append(groupName); d->groups[groupName]; return true; } bool KoColorSet::moveGroup(const QString &groupName, const QString &groupNameInsertBefore) { if (d->groupNames.contains(groupName)==false || d->groupNames.contains(groupNameInsertBefore)==false) { return false; } d->groupNames.removeAt(d->groupNames.indexOf(groupName)); int index = d->groupNames.size(); if (groupNameInsertBefore!=QString()) { index = d->groupNames.indexOf(groupNameInsertBefore); } d->groupNames.insert(index, groupName); return true; } bool KoColorSet::removeGroup(const QString &groupName, bool keepColors) { if (!d->groups.contains(groupName)) { return false; } if (keepColors) { for (int i = 0; igroups.value(groupName).size(); i++) { d->colors.append(d->groups.value(groupName).at(i)); } } for(int n = 0; ngroupNames.size(); n++) { if (d->groupNames.at(n) == groupName) { d->groupNames.removeAt(n); } } d->groups.remove(groupName); return true; } QString KoColorSet::defaultFileExtension() const { return QString(".kpl"); } bool KoColorSet::loadGpl() { QString s = QString::fromUtf8(d->data.data(), d->data.count()); if (s.isEmpty() || s.isNull() || s.length() < 50) { warnPigment << "Illegal Gimp palette file: " << filename(); return false; } quint32 index = 0; QStringList lines = s.split('\n', QString::SkipEmptyParts); if (lines.size() < 3) { return false; } QString columns; qint32 r, g, b; KoColorSetEntry e; // Read name if (!lines[0].startsWith("GIMP") || !lines[1].startsWith("Name: ")) { warnPigment << "Illegal Gimp palette file: " << filename(); return false; } setName(i18n(lines[1].mid(strlen("Name: ")).trimmed().toLatin1())); index = 2; // Read columns if (lines[index].startsWith("Columns: ")) { columns = lines[index].mid(strlen("Columns: ")).trimmed(); d->columns = columns.toInt(); index = 3; } for (qint32 i = index; i < lines.size(); i++) { if (lines[i].startsWith('#')) { d->comment += lines[i].mid(1).trimmed() + ' '; } else if (!lines[i].isEmpty()) { QStringList a = lines[i].replace('\t', ' ').split(' ', QString::SkipEmptyParts); if (a.count() < 3) { break; } r = a[0].toInt(); a.pop_front(); g = a[0].toInt(); a.pop_front(); b = a[0].toInt(); a.pop_front(); r = qBound(0, r, 255); g = qBound(0, g, 255); b = qBound(0, b, 255); e.color = KoColor(KoColorSpaceRegistry::instance()->rgb8()); e.color.fromQColor(QColor(r, g, b)); QString name = a.join(" "); e.name = name.isEmpty() ? i18n("Untitled") : name; add(e); } } return true; } bool KoColorSet::loadAct() { QFileInfo info(filename()); setName(info.baseName()); KoColorSetEntry e; for (int i = 0; i < d->data.size(); i += 3) { quint8 r = d->data[i]; quint8 g = d->data[i+1]; quint8 b = d->data[i+2]; e.color = KoColor(KoColorSpaceRegistry::instance()->rgb8()); e.color.fromQColor(QColor(r, g, b)); add(e); } return true; } struct RiffHeader { quint32 riff; quint32 size; quint32 signature; quint32 data; quint32 datasize; quint16 version; quint16 colorcount; }; bool KoColorSet::loadRiff() { // http://worms2d.info/Palette_file QFileInfo info(filename()); setName(info.baseName()); KoColorSetEntry e; RiffHeader header; memcpy(&header, d->data.constData(), sizeof(RiffHeader)); header.colorcount = qFromBigEndian(header.colorcount); for (int i = sizeof(RiffHeader); (i < (int)(sizeof(RiffHeader) + header.colorcount) && i < d->data.size()); i += 4) { quint8 r = d->data[i]; quint8 g = d->data[i+1]; quint8 b = d->data[i+2]; e.color = KoColor(KoColorSpaceRegistry::instance()->rgb8()); e.color.fromQColor(QColor(r, g, b)); add(e); } return true; } bool KoColorSet::loadPsp() { QFileInfo info(filename()); setName(info.baseName()); KoColorSetEntry e; qint32 r, g, b; QString s = QString::fromUtf8(d->data.data(), d->data.count()); QStringList l = s.split('\n', QString::SkipEmptyParts); if (l.size() < 4) return false; if (l[0] != "JASC-PAL") return false; if (l[1] != "0100") return false; int entries = l[2].toInt(); for (int i = 0; i < entries; ++i) { QStringList a = l[i + 3].replace('\t', ' ').split(' ', QString::SkipEmptyParts); if (a.count() != 3) { continue; } r = a[0].toInt(); a.pop_front(); g = a[0].toInt(); a.pop_front(); b = a[0].toInt(); a.pop_front(); r = qBound(0, r, 255); g = qBound(0, g, 255); b = qBound(0, b, 255); e.color = KoColor(KoColorSpaceRegistry::instance()->rgb8()); e.color.fromQColor(QColor(r, g, b)); QString name = a.join(" "); e.name = name.isEmpty() ? i18n("Untitled") : name; add(e); } return true; } void scribusParseColor(KoColorSet *set, QXmlStreamReader *xml) { KoColorSetEntry currentColor; //It's a color, retrieve it QXmlStreamAttributes colorProperties = xml->attributes(); QStringRef colorValue; // RGB or CMYK? if (colorProperties.hasAttribute("RGB")) { dbgPigment << "Color " << colorProperties.value("NAME") << ", RGB " << colorProperties.value("RGB"); QStringRef colorName = colorProperties.value("NAME"); currentColor.name = colorName.isEmpty() || colorName.isNull() ? i18n("Untitled") : colorName.toString(); currentColor.color = KoColor(KoColorSpaceRegistry::instance()->rgb8()); colorValue = colorProperties.value("RGB"); if (colorValue.length() != 7 && colorValue.at(0) != '#') { // Color is a hexadecimal number xml->raiseError("Invalid rgb8 color (malformed): " + colorValue); return; } else { bool rgbOk; quint32 rgb = colorValue.mid(1).toUInt(&rgbOk, 16); if (!rgbOk) { xml->raiseError("Invalid rgb8 color (unable to convert): " + colorValue); return; } quint8 r = rgb >> 16 & 0xff; quint8 g = rgb >> 8 & 0xff; quint8 b = rgb & 0xff; dbgPigment << "Color parsed: "<< r << g << b; currentColor.color.data()[0] = r; currentColor.color.data()[1] = g; currentColor.color.data()[2] = b; currentColor.color.setOpacity(OPACITY_OPAQUE_U8); set->add(currentColor); while(xml->readNextStartElement()) { //ignore - these are all unknown or the /> element tag xml->skipCurrentElement(); } return; } } else if (colorProperties.hasAttribute("CMYK")) { dbgPigment << "Color " << colorProperties.value("NAME") << ", CMYK " << colorProperties.value("CMYK"); QStringRef colorName = colorProperties.value("NAME"); currentColor.name = colorName.isEmpty() || colorName.isNull() ? i18n("Untitled") : colorName.toString(); currentColor.color = KoColor(KoColorSpaceRegistry::instance()->colorSpace(CMYKAColorModelID.id(), Integer8BitsColorDepthID.id(), QString())); colorValue = colorProperties.value("CMYK"); if (colorValue.length() != 9 && colorValue.at(0) != '#') { // Color is a hexadecimal number xml->raiseError("Invalid cmyk color (malformed): " % colorValue); return; } else { bool cmykOk; quint32 cmyk = colorValue.mid(1).toUInt(&cmykOk, 16); // cmyk uses the full 32 bits if (!cmykOk) { xml->raiseError("Invalid cmyk color (unable to convert): " % colorValue); return; } quint8 c = cmyk >> 24 & 0xff; quint8 m = cmyk >> 16 & 0xff; quint8 y = cmyk >> 8 & 0xff; quint8 k = cmyk & 0xff; dbgPigment << "Color parsed: "<< c << m << y << k; currentColor.color.data()[0] = c; currentColor.color.data()[1] = m; currentColor.color.data()[2] = y; currentColor.color.data()[3] = k; currentColor.color.setOpacity(OPACITY_OPAQUE_U8); set->add(currentColor); while(xml->readNextStartElement()) { //ignore - these are all unknown or the /> element tag xml->skipCurrentElement(); } return; } } else { xml->raiseError("Unknown color space for color " + currentColor.name); } } bool loadScribusXmlPalette(KoColorSet *set, QXmlStreamReader *xml) { //1. Get name QXmlStreamAttributes paletteProperties = xml->attributes(); QStringRef paletteName = paletteProperties.value("Name"); dbgPigment << "Processed name of palette:" << paletteName; set->setName(paletteName.toString()); //2. Inside the SCRIBUSCOLORS, there are lots of colors. Retrieve them while(xml->readNextStartElement()) { QStringRef currentElement = xml->name(); if(QStringRef::compare(currentElement, "COLOR", Qt::CaseInsensitive) == 0) { scribusParseColor(set, xml); } else { xml->skipCurrentElement(); } } if(xml->hasError()) { return false; } return true; } bool KoColorSet::loadXml() { bool res = false; QXmlStreamReader *xml = new QXmlStreamReader(d->data); if (xml->readNextStartElement()) { QStringRef paletteId = xml->name(); if (QStringRef::compare(paletteId, "SCRIBUSCOLORS", Qt::CaseInsensitive) == 0) { // Scribus dbgPigment << "XML palette: " << filename() << ", Scribus format"; res = loadScribusXmlPalette(this, xml); } else { // Unknown XML format xml->raiseError("Unknown XML palette format. Expected SCRIBUSCOLORS, found " + paletteId); } } // If there is any error (it should be returned through the stream) if (xml->hasError() || !res) { warnPigment << "Illegal XML palette:" << filename(); warnPigment << "Error (line"<< xml->lineNumber() << ", column" << xml->columnNumber() << "):" << xml->errorString(); return false; } else { dbgPigment << "XML palette parsed successfully:" << filename(); return true; } } bool KoColorSet::saveKpl(QIODevice *dev) const { QScopedPointer store(KoStore::createStore(dev, KoStore::Write, "application/x-krita-palette", KoStore::Zip)); if (!store || store->bad()) return false; QSet profiles; QMap profileMap; { QDomDocument doc; QDomElement root = doc.createElement("Colorset"); root.setAttribute("version", "1.0"); root.setAttribute("name", name()); root.setAttribute("comment", d->comment); root.setAttribute("columns", d->columns); Q_FOREACH(const KoColorSetEntry &entry, d->colors) { // Only save non-builtin profiles.= const KoColorProfile *profile = entry.color.colorSpace()->profile(); if (!profile->fileName().isEmpty()) { profiles << profile; profileMap[profile] = entry.color.colorSpace(); } QDomElement el = doc.createElement("ColorSetEntry"); el.setAttribute("name", entry.name); el.setAttribute("id", entry.id); el.setAttribute("spot", entry.spotColor ? "true" : "false"); el.setAttribute("bitdepth", entry.color.colorSpace()->colorDepthId().id()); entry.color.toXML(doc, el); root.appendChild(el); } Q_FOREACH(const QString &groupName, d->groupNames) { QDomElement gl = doc.createElement("Group"); gl.setAttribute("name", groupName); root.appendChild(gl); Q_FOREACH(const KoColorSetEntry &entry, d->groups.value(groupName)) { // Only save non-builtin profiles.= const KoColorProfile *profile = entry.color.colorSpace()->profile(); if (!profile->fileName().isEmpty()) { profiles << profile; profileMap[profile] = entry.color.colorSpace(); } QDomElement el = doc.createElement("ColorSetEntry"); el.setAttribute("name", entry.name); el.setAttribute("id", entry.id); el.setAttribute("spot", entry.spotColor ? "true" : "false"); el.setAttribute("bitdepth", entry.color.colorSpace()->colorDepthId().id()); entry.color.toXML(doc, el); gl.appendChild(el); } } doc.appendChild(root); if (!store->open("colorset.xml")) { return false; } QByteArray ba = doc.toByteArray(); if (store->write(ba) != ba.size()) { return false; } if (!store->close()) { return false; } } QDomDocument doc; QDomElement profileElement = doc.createElement("Profiles"); Q_FOREACH(const KoColorProfile *profile, profiles) { QString fn = QFileInfo(profile->fileName()).fileName(); if (!store->open(fn)) { return false; } QByteArray profileRawData = profile->rawData(); if (!store->write(profileRawData)) { return false; } if (!store->close()) { return false; } QDomElement el = doc.createElement("Profile"); el.setAttribute("filename", fn); el.setAttribute("name", profile->name()); el.setAttribute("colorModelId", profileMap[profile]->colorModelId().id()); el.setAttribute("colorDepthId", profileMap[profile]->colorDepthId().id()); profileElement.appendChild(el); } doc.appendChild(profileElement); if (!store->open("profiles.xml")) { return false; } QByteArray ba = doc.toByteArray(); if (store->write(ba) != ba.size()) { return false; } if (!store->close()) { return false; } return store->finalize(); } bool KoColorSet::loadKpl() { QBuffer buf(&d->data); buf.open(QBuffer::ReadOnly); QScopedPointer store(KoStore::createStore(&buf, KoStore::Read, "application/x-krita-palette", KoStore::Zip)); if (!store || store->bad()) return false; if (store->hasFile("profiles.xml")) { if (!store->open("profiles.xml")) { return false; } QByteArray data; data.resize(store->size()); QByteArray ba = store->read(store->size()); store->close(); QDomDocument doc; doc.setContent(ba); QDomElement e = doc.documentElement(); QDomElement c = e.firstChildElement("Profiles"); while (!c.isNull()) { QString name = c.attribute("name"); QString filename = c.attribute("filename"); QString colorModelId = c.attribute("colorModelId"); QString colorDepthId = c.attribute("colorDepthId"); if (!KoColorSpaceRegistry::instance()->profileByName(name)) { store->open(filename); QByteArray data; data.resize(store->size()); data = store->read(store->size()); store->close(); const KoColorProfile *profile = KoColorSpaceRegistry::instance()->createColorProfile(colorModelId, colorDepthId, data); if (profile && profile->valid()) { KoColorSpaceRegistry::instance()->addProfile(profile); } } c = c.nextSiblingElement(); } } { if (!store->open("colorset.xml")) { return false; } QByteArray data; data.resize(store->size()); QByteArray ba = store->read(store->size()); store->close(); QDomDocument doc; doc.setContent(ba); QDomElement e = doc.documentElement(); setName(e.attribute("name")); d->comment = e.attribute("comment"); d->columns = e.attribute("columns").toInt(); QDomElement c = e.firstChildElement("ColorSetEntry"); while (!c.isNull()) { QString colorDepthId = c.attribute("bitdepth", Integer8BitsColorDepthID.id()); KoColorSetEntry entry; entry.color = KoColor::fromXML(c.firstChildElement(), colorDepthId); entry.name = c.attribute("name"); entry.id = c.attribute("id"); entry.spotColor = c.attribute("spot", "false") == "true" ? true : false; d->colors << entry; c = c.nextSiblingElement("ColorSetEntry"); } QDomElement g = e.firstChildElement("Group"); while (!g.isNull()) { QString groupName = g.attribute("name"); addGroup(groupName); QDomElement cg = g.firstChildElement("ColorSetEntry"); while (!cg.isNull()) { QString colorDepthId = cg.attribute("bitdepth", Integer8BitsColorDepthID.id()); KoColorSetEntry entry; entry.color = KoColor::fromXML(cg.firstChildElement(), colorDepthId); entry.name = cg.attribute("name"); entry.id = cg.attribute("id"); entry.spotColor = cg.attribute("spot", "false") == "true" ? true : false; add(entry, groupName); cg = cg.nextSiblingElement("ColorSetEntry"); } g = g.nextSiblingElement("Group"); } } buf.close(); return true; } quint16 readShort(QIODevice *io) { quint16 val; quint64 read = io->read((char*)&val, 2); if (read != 2) return false; return qFromBigEndian(val); } bool KoColorSet::loadAco() { QFileInfo info(filename()); setName(info.baseName()); QBuffer buf(&d->data); buf.open(QBuffer::ReadOnly); quint16 version = readShort(&buf); quint16 numColors = readShort(&buf); KoColorSetEntry e; if (version == 1 && buf.size() > 4+numColors*10) { buf.seek(4+numColors*10); version = readShort(&buf); numColors = readShort(&buf); } const quint16 quint16_MAX = 65535; for (int i = 0; i < numColors && !buf.atEnd(); ++i) { quint16 colorSpace = readShort(&buf); quint16 ch1 = readShort(&buf); quint16 ch2 = readShort(&buf); quint16 ch3 = readShort(&buf); quint16 ch4 = readShort(&buf); bool skip = false; if (colorSpace == 0) { // RGB const KoColorProfile *srgb = KoColorSpaceRegistry::instance()->rgb8()->profile(); e.color = KoColor(KoColorSpaceRegistry::instance()->rgb16(srgb)); reinterpret_cast(e.color.data())[0] = ch3; reinterpret_cast(e.color.data())[1] = ch2; reinterpret_cast(e.color.data())[2] = ch1; e.color.setOpacity(OPACITY_OPAQUE_U8); } else if (colorSpace == 1) { // HSB e.color = KoColor(KoColorSpaceRegistry::instance()->rgb16()); QColor c; c.setHsvF(ch1 / 65536.0, ch2 / 65536.0, ch3 / 65536.0); e.color.fromQColor(c); e.color.setOpacity(OPACITY_OPAQUE_U8); } else if (colorSpace == 2) { // CMYK e.color = KoColor(KoColorSpaceRegistry::instance()->colorSpace(CMYKAColorModelID.id(), Integer16BitsColorDepthID.id(), QString())); reinterpret_cast(e.color.data())[0] = quint16_MAX - ch1; reinterpret_cast(e.color.data())[1] = quint16_MAX - ch2; reinterpret_cast(e.color.data())[2] = quint16_MAX - ch3; reinterpret_cast(e.color.data())[3] = quint16_MAX - ch4; e.color.setOpacity(OPACITY_OPAQUE_U8); } else if (colorSpace == 7) { // LAB e.color = KoColor(KoColorSpaceRegistry::instance()->lab16()); reinterpret_cast(e.color.data())[0] = ch3; reinterpret_cast(e.color.data())[1] = ch2; reinterpret_cast(e.color.data())[2] = ch1; e.color.setOpacity(OPACITY_OPAQUE_U8); } else if (colorSpace == 8) { // GRAY e.color = KoColor(KoColorSpaceRegistry::instance()->colorSpace(GrayAColorModelID.id(), Integer16BitsColorDepthID.id(), QString())); reinterpret_cast(e.color.data())[0] = ch1 * (quint16_MAX / 10000); e.color.setOpacity(OPACITY_OPAQUE_U8); } else { warnPigment << "Unsupported colorspace in palette" << filename() << "(" << colorSpace << ")"; skip = true; } if (version == 2) { quint16 v2 = readShort(&buf); //this isn't a version, it's a marker and needs to be skipped. Q_UNUSED(v2); quint16 size = readShort(&buf) -1; //then comes the length if (size>0) { QByteArray ba = buf.read(size*2); if (ba.size() == size*2) { QTextCodec *Utf16Codec = QTextCodec::codecForName("UTF-16BE"); e.name = Utf16Codec->toUnicode(ba); } else { warnPigment << "Version 2 name block is the wrong size" << filename(); } } v2 = readShort(&buf); //end marker also needs to be skipped. Q_UNUSED(v2); } if (!skip) { add(e); } } return true; } bool KoColorSet::loadSbz() { QBuffer buf(&d->data); buf.open(QBuffer::ReadOnly); // &buf is a subclass of QIODevice QScopedPointer store(KoStore::createStore(&buf, KoStore::Read, "application/x-swatchbook", KoStore::Zip)); if (!store || store->bad()) return false; if (store->hasFile("swatchbook.xml")) { // Try opening... if (!store->open("swatchbook.xml")) { return false; } QByteArray data; data.resize(store->size()); QByteArray ba = store->read(store->size()); store->close(); dbgPigment << "XML palette: " << filename() << ", SwatchBooker format"; QDomDocument doc; int errorLine, errorColumn; QString errorMessage; bool status = doc.setContent(ba, &errorMessage, &errorLine, &errorColumn); if (!status) { warnPigment << "Illegal XML palette:" << filename(); warnPigment << "Error (line" << errorLine << ", column" << errorColumn << "):" << errorMessage; return false; } QDomElement e = doc.documentElement(); // SwatchBook // Start reading properties... QDomElement metadata = e.firstChildElement("metadata"); if (e.isNull()) { warnPigment << "Palette metadata not found"; return false; } QDomElement title = metadata.firstChildElement("dc:title"); QString colorName = title.text(); colorName = colorName.isEmpty() ? i18n("Untitled") : colorName; setName(colorName); dbgPigment << "Processed name of palette:" << name(); // End reading properties // Now read colors... QDomElement materials = e.firstChildElement("materials"); if (materials.isNull()) { warnPigment << "Materials (color definitions) not found"; return false; } // This one has lots of "color" elements QDomElement colorElement = materials.firstChildElement("color"); if (colorElement.isNull()) { warnPigment << "Color definitions not found (line" << materials.lineNumber() << ", column" << materials.columnNumber() << ")"; return false; } // Also read the swatch book... QDomElement book = e.firstChildElement("book"); if (book.isNull()) { warnPigment << "Palette book (swatch composition) not found (line" << e.lineNumber() << ", column" << e.columnNumber() << ")"; return false; } // Which has lots of "swatch"es (todo: support groups) QDomElement swatch = book.firstChildElement(); if (swatch.isNull()) { warnPigment << "Swatches/groups definition not found (line" << book.lineNumber() << ", column" << book.columnNumber() << ")"; return false; } // We'll store colors here, and as we process swatches // we'll add them to the palette QHash materialsBook; QHash fileColorSpaces; // Color processing for(; !colorElement.isNull(); colorElement = colorElement.nextSiblingElement("color")) { KoColorSetEntry currentColor; // Set if color is spot currentColor.spotColor = colorElement.attribute("usage") == "spot"; // inside contains id and name // one or more define the color QDomElement currentColorMetadata = colorElement.firstChildElement("metadata"); QDomNodeList currentColorValues = colorElement.elementsByTagName("values"); // Get color name QDomElement colorTitle = currentColorMetadata.firstChildElement("dc:title"); QDomElement colorId = currentColorMetadata.firstChildElement("dc:identifier"); // Is there an id? (we need that at the very least for identifying a color) if (colorId.text().isEmpty()) { warnPigment << "Unidentified color (line" << colorId.lineNumber()<< ", column" << colorId.columnNumber() << ")"; return false; } if (materialsBook.contains(colorId.text())) { warnPigment << "Duplicated color definition (line" << colorId.lineNumber()<< ", column" << colorId.columnNumber() << ")"; return false; } // Get a valid color name currentColor.id = colorId.text(); currentColor.name = colorTitle.text().isEmpty() ? colorId.text() : colorTitle.text(); // Get a valid color definition if (currentColorValues.isEmpty()) { warnPigment << "Color definitions not found (line" << colorElement.lineNumber() << ", column" << colorElement.columnNumber() << ")"; return false; } bool firstDefinition = false; const KoColorProfile *srgb = KoColorSpaceRegistry::instance()->rgb8()->profile(); // Priority: Lab, otherwise the first definition found for(int j = 0; j < currentColorValues.size(); j++) { QDomNode colorValue = currentColorValues.at(j); QDomElement colorValueE = colorValue.toElement(); QString model = colorValueE.attribute("model", QString()); // sRGB,RGB,HSV,HSL,CMY,CMYK,nCLR: 0 -> 1 // YIQ: Y 0 -> 1 : IQ -0.5 -> 0.5 // Lab: L 0 -> 100 : ab -128 -> 127 // XYZ: 0 -> ~100 if (model == "Lab") { QStringList lab = colorValueE.text().split(" "); if (lab.length() != 3) { warnPigment << "Invalid Lab color definition (line" << colorValueE.lineNumber() << ", column" << colorValueE.columnNumber() << ")"; } float l = lab.at(0).toFloat(&status); float a = lab.at(1).toFloat(&status); float b = lab.at(2).toFloat(&status); if (!status) { warnPigment << "Invalid float definition (line" << colorValueE.lineNumber() << ", column" << colorValueE.columnNumber() << ")"; } currentColor.color = KoColor(KoColorSpaceRegistry::instance()->colorSpace(LABAColorModelID.id(), Float32BitsColorDepthID.id(), QString())); reinterpret_cast(currentColor.color.data())[0] = l; reinterpret_cast(currentColor.color.data())[1] = a; reinterpret_cast(currentColor.color.data())[2] = b; currentColor.color.setOpacity(OPACITY_OPAQUE_F); firstDefinition = true; break; // Immediately add this one } else if (model == "sRGB" && !firstDefinition) { QStringList rgb = colorValueE.text().split(" "); if (rgb.length() != 3) { warnPigment << "Invalid sRGB color definition (line" << colorValueE.lineNumber() << ", column" << colorValueE.columnNumber() << ")"; } float r = rgb.at(0).toFloat(&status); float g = rgb.at(1).toFloat(&status); float b = rgb.at(2).toFloat(&status); if (!status) { warnPigment << "Invalid float definition (line" << colorValueE.lineNumber() << ", column" << colorValueE.columnNumber() << ")"; } currentColor.color = KoColor(KoColorSpaceRegistry::instance()->colorSpace(RGBAColorModelID.id(), Float32BitsColorDepthID.id(), srgb)); reinterpret_cast(currentColor.color.data())[0] = r; reinterpret_cast(currentColor.color.data())[1] = g; reinterpret_cast(currentColor.color.data())[2] = b; currentColor.color.setOpacity(OPACITY_OPAQUE_F); firstDefinition = true; } else if (model == "XYZ" && !firstDefinition) { QStringList xyz = colorValueE.text().split(" "); if (xyz.length() != 3) { warnPigment << "Invalid XYZ color definition (line" << colorValueE.lineNumber() << ", column" << colorValueE.columnNumber() << ")"; } float x = xyz.at(0).toFloat(&status); float y = xyz.at(1).toFloat(&status); float z = xyz.at(2).toFloat(&status); if (!status) { warnPigment << "Invalid float definition (line" << colorValueE.lineNumber() << ", column" << colorValueE.columnNumber() << ")"; } currentColor.color = KoColor(KoColorSpaceRegistry::instance()->colorSpace(XYZAColorModelID.id(), Float32BitsColorDepthID.id(), QString())); reinterpret_cast(currentColor.color.data())[0] = x; reinterpret_cast(currentColor.color.data())[1] = y; reinterpret_cast(currentColor.color.data())[2] = z; currentColor.color.setOpacity(OPACITY_OPAQUE_F); firstDefinition = true; } // The following color spaces admit an ICC profile (in SwatchBooker) else if (model == "CMYK" && !firstDefinition) { QStringList cmyk = colorValueE.text().split(" "); if (cmyk.length() != 4) { warnPigment << "Invalid CMYK color definition (line" << colorValueE.lineNumber() << ", column" << colorValueE.columnNumber() << ")"; } float c = cmyk.at(0).toFloat(&status); float m = cmyk.at(1).toFloat(&status); float y = cmyk.at(2).toFloat(&status); float k = cmyk.at(3).toFloat(&status); if (!status) { warnPigment << "Invalid float definition (line" << colorValueE.lineNumber() << ", column" << colorValueE.columnNumber() << ")"; } QString space = colorValueE.attribute("space"); const KoColorSpace *colorSpace = KoColorSpaceRegistry::instance()->colorSpace(CMYKAColorModelID.id(), Float32BitsColorDepthID.id(), QString()); if (!space.isEmpty()) { // Try loading the profile and add it to the registry if (!fileColorSpaces.contains(space)) { store->enterDirectory("profiles"); store->open(space); QByteArray data; data.resize(store->size()); data = store->read(store->size()); store->close(); const KoColorProfile *profile = KoColorSpaceRegistry::instance()->createColorProfile(CMYKAColorModelID.id(), Float32BitsColorDepthID.id(), data); if (profile && profile->valid()) { KoColorSpaceRegistry::instance()->addProfile(profile); colorSpace = KoColorSpaceRegistry::instance()->colorSpace(CMYKAColorModelID.id(), Float32BitsColorDepthID.id(), profile); fileColorSpaces.insert(space, colorSpace); } } else { colorSpace = fileColorSpaces.value(space); } } currentColor.color = KoColor(colorSpace); reinterpret_cast(currentColor.color.data())[0] = c; reinterpret_cast(currentColor.color.data())[1] = m; reinterpret_cast(currentColor.color.data())[2] = y; reinterpret_cast(currentColor.color.data())[3] = k; currentColor.color.setOpacity(OPACITY_OPAQUE_F); firstDefinition = true; } else if (model == "GRAY" && !firstDefinition) { QString gray = colorValueE.text(); float g = gray.toFloat(&status); if (!status) { warnPigment << "Invalid float definition (line" << colorValueE.lineNumber() << ", column" << colorValueE.columnNumber() << ")"; } QString space = colorValueE.attribute("space"); const KoColorSpace *colorSpace = KoColorSpaceRegistry::instance()->colorSpace(GrayAColorModelID.id(), Float32BitsColorDepthID.id(), QString()); if (!space.isEmpty()) { // Try loading the profile and add it to the registry if (!fileColorSpaces.contains(space)) { store->enterDirectory("profiles"); store->open(space); QByteArray data; data.resize(store->size()); data = store->read(store->size()); store->close(); const KoColorProfile *profile = KoColorSpaceRegistry::instance()->createColorProfile(CMYKAColorModelID.id(), Float32BitsColorDepthID.id(), data); if (profile && profile->valid()) { KoColorSpaceRegistry::instance()->addProfile(profile); colorSpace = KoColorSpaceRegistry::instance()->colorSpace(CMYKAColorModelID.id(), Float32BitsColorDepthID.id(), profile); fileColorSpaces.insert(space, colorSpace); } } else { colorSpace = fileColorSpaces.value(space); } } currentColor.color = KoColor(colorSpace); reinterpret_cast(currentColor.color.data())[0] = g; currentColor.color.setOpacity(OPACITY_OPAQUE_F); firstDefinition = true; } else if (model == "RGB" && !firstDefinition) { QStringList rgb = colorValueE.text().split(" "); if (rgb.length() != 3) { warnPigment << "Invalid RGB color definition (line" << colorValueE.lineNumber() << ", column" << colorValueE.columnNumber() << ")"; } float r = rgb.at(0).toFloat(&status); float g = rgb.at(1).toFloat(&status); float b = rgb.at(2).toFloat(&status); if (!status) { warnPigment << "Invalid float definition (line" << colorValueE.lineNumber() << ", column" << colorValueE.columnNumber() << ")"; } QString space = colorValueE.attribute("space"); const KoColorSpace *colorSpace = KoColorSpaceRegistry::instance()->colorSpace(RGBAColorModelID.id(), Float32BitsColorDepthID.id(), srgb); if (!space.isEmpty()) { // Try loading the profile and add it to the registry if (!fileColorSpaces.contains(space)) { store->enterDirectory("profiles"); store->open(space); QByteArray data; data.resize(store->size()); data = store->read(store->size()); store->close(); const KoColorProfile *profile = KoColorSpaceRegistry::instance()->createColorProfile(RGBAColorModelID.id(), Float32BitsColorDepthID.id(), data); if (profile && profile->valid()) { KoColorSpaceRegistry::instance()->addProfile(profile); colorSpace = KoColorSpaceRegistry::instance()->colorSpace(RGBAColorModelID.id(), Float32BitsColorDepthID.id(), profile); fileColorSpaces.insert(space, colorSpace); } } else { colorSpace = fileColorSpaces.value(space); } } currentColor.color = KoColor(colorSpace); reinterpret_cast(currentColor.color.data())[0] = r; reinterpret_cast(currentColor.color.data())[1] = g; reinterpret_cast(currentColor.color.data())[2] = b; currentColor.color.setOpacity(OPACITY_OPAQUE_F); firstDefinition = true; } else { warnPigment << "Color space not implemented:" << model << "(line" << colorValueE.lineNumber() << ", column "<< colorValueE.columnNumber() << ")"; } } if (firstDefinition) { materialsBook.insert(currentColor.id, currentColor); } else { warnPigment << "No supported color spaces for the current color (line" << colorElement.lineNumber() << ", column "<< colorElement.columnNumber() << ")"; return false; } } // End colors // Now decide which ones will go into the palette for(;!swatch.isNull(); swatch = swatch.nextSiblingElement()) { QString type = swatch.tagName(); if (type.isEmpty() || type.isNull()) { warnPigment << "Invalid swatch/group definition (no id) (line" << swatch.lineNumber() << ", column" << swatch.columnNumber() << ")"; return false; } else if (type == "swatch") { QString id = swatch.attribute("material"); if (id.isEmpty() || id.isNull()) { warnPigment << "Invalid swatch definition (no material id) (line" << swatch.lineNumber() << ", column" << swatch.columnNumber() << ")"; return false; } if (materialsBook.contains(id)) { add(materialsBook.value(id)); } else { warnPigment << "Invalid swatch definition (material not found) (line" << swatch.lineNumber() << ", column" << swatch.columnNumber() << ")"; return false; } } else if (type == "group") { QDomElement groupMetadata = swatch.firstChildElement("metadata"); if (groupMetadata.isNull()) { warnPigment << "Invalid group definition (missing metadata) (line" << groupMetadata.lineNumber() << ", column" << groupMetadata.columnNumber() << ")"; return false; } QDomElement groupTitle = metadata.firstChildElement("dc:title"); if (groupTitle.isNull()) { warnPigment << "Invalid group definition (missing title) (line" << groupTitle.lineNumber() << ", column" << groupTitle.columnNumber() << ")"; return false; } QString currentGroupName = groupTitle.text(); QDomElement groupSwatch = swatch.firstChildElement("swatch"); while(!groupSwatch.isNull()) { QString id = groupSwatch.attribute("material"); if (id.isEmpty() || id.isNull()) { warnPigment << "Invalid swatch definition (no material id) (line" << groupSwatch.lineNumber() << ", column" << groupSwatch.columnNumber() << ")"; return false; } if (materialsBook.contains(id)) { add(materialsBook.value(id), currentGroupName); } else { warnPigment << "Invalid swatch definition (material not found) (line" << groupSwatch.lineNumber() << ", column" << groupSwatch.columnNumber() << ")"; return false; } groupSwatch = groupSwatch.nextSiblingElement("swatch"); } } } // End palette } buf.close(); return true; } diff --git a/libs/ui/KisMainWindow.cpp b/libs/ui/KisMainWindow.cpp index e33a5943d7..7df46e855b 100644 --- a/libs/ui/KisMainWindow.cpp +++ b/libs/ui/KisMainWindow.cpp @@ -1,2408 +1,2487 @@ /* This file is part of the KDE project Copyright (C) 1998, 1999 Torben Weis Copyright (C) 2000-2006 David Faure Copyright (C) 2007, 2009 Thomas zander Copyright (C) 2010 Benjamin Port This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KisMainWindow.h" #include // qt includes #include #include #include #include #include +#include #include #include +#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include +#include #ifdef HAVE_KIO #include #endif #include #include #include #include #include #include #include #include #include #include #include "KoDockFactoryBase.h" #include "KoDockWidgetTitleBar.h" #include "KoDocumentInfoDlg.h" #include "KoDocumentInfo.h" #include "KoFileDialog.h" #include #include #include #include #include #include "KoToolDocker.h" #include #include #include #include +#include #include #include #include "dialogs/kis_about_application.h" #include "dialogs/kis_delayed_save_dialog.h" #include "dialogs/kis_dlg_preferences.h" #include "kis_action.h" #include "kis_action_manager.h" #include "KisApplication.h" #include "kis_canvas2.h" #include "kis_canvas_controller.h" #include "kis_canvas_resource_provider.h" #include "kis_clipboard.h" #include "kis_config.h" #include "kis_config_notifier.h" #include "kis_custom_image_widget.h" #include #include "KisDocument.h" #include "KisDocument.h" #include "kis_group_layer.h" #include "kis_icon_utils.h" #include "kis_image_from_clipboard_widget.h" #include "kis_image.h" #include #include "KisImportExportManager.h" #include "kis_mainwindow_observer.h" #include "kis_node.h" #include "KisOpenPane.h" #include "kis_paintop_box.h" #include "KisPart.h" #include "KisPrintJob.h" #include "kis_resource_server_provider.h" #include "kis_signal_compressor_with_param.h" #include "KisView.h" #include "KisViewManager.h" #include "thememanager.h" #include "kis_animation_importer.h" #include "dialogs/kis_dlg_import_image_sequence.h" #include "kis_animation_exporter.h" #include #ifdef Q_OS_WIN #include #endif class ToolDockerFactory : public KoDockFactoryBase { public: ToolDockerFactory() : KoDockFactoryBase() { } QString id() const override { return "sharedtooldocker"; } QDockWidget* createDockWidget() override { KoToolDocker* dockWidget = new KoToolDocker(); dockWidget->setTabEnabled(false); return dockWidget; } DockPosition defaultDockPosition() const override { return DockRight; } }; class Q_DECL_HIDDEN KisMainWindow::Private { public: Private(KisMainWindow *parent) : q(parent) , dockWidgetMenu(new KActionMenu(i18nc("@action:inmenu", "&Dockers"), parent)) , windowMenu(new KActionMenu(i18nc("@action:inmenu", "&Window"), parent)) , documentMenu(new KActionMenu(i18nc("@action:inmenu", "New &View"), parent)) + , workspaceMenu(new KActionMenu(i18nc("@action:inmenu", "Wor&kspace"), parent)) , mdiArea(new QMdiArea(parent)) , windowMapper(new QSignalMapper(parent)) , documentMapper(new QSignalMapper(parent)) { } ~Private() { qDeleteAll(toolbarList); } KisMainWindow *q {0}; KisViewManager *viewManager {0}; QPointer activeView; QPointer progress; QPointer progressCancel; QMutex progressMutex; QList toolbarList; bool firstTime {true}; bool windowSizeDirty {false}; bool readOnly {false}; bool isImporting {false}; bool isExporting {false}; bool noCleanup {false}; KisAction *showDocumentInfo {0}; KisAction *saveAction {0}; KisAction *saveActionAs {0}; // KisAction *printAction; // KisAction *printActionPreview; // KisAction *exportPdf {0}; KisAction *importAnimation {0}; KisAction *closeAll {0}; // KisAction *reloadFile; KisAction *importFile {0}; KisAction *exportFile {0}; KisAction *undo {0}; KisAction *redo {0}; KisAction *newWindow {0}; KisAction *close {0}; KisAction *mdiCascade {0}; KisAction *mdiTile {0}; KisAction *mdiNextWindow {0}; KisAction *mdiPreviousWindow {0}; KisAction *toggleDockers {0}; KisAction *toggleDockerTitleBars {0}; KisAction *expandingSpacers[2]; KActionMenu *dockWidgetMenu; KActionMenu *windowMenu; KActionMenu *documentMenu; + KActionMenu *workspaceMenu; KHelpMenu *helpMenu {0}; KRecentFilesAction *recentFiles {0}; + KoResourceModel *workspacemodel {0}; QUrl lastExportUrl; QMap dockWidgetsMap; QMap dockWidgetVisibilityMap; QByteArray dockerStateBeforeHiding; KoToolDocker *toolOptionsDocker {0}; QCloseEvent *deferredClosingEvent {0}; Digikam::ThemeManager *themeManager {0}; QMdiArea *mdiArea; QMdiSubWindow *activeSubWindow {0}; QSignalMapper *windowMapper; QSignalMapper *documentMapper; QByteArray lastExportedFormat; QScopedPointer > tabSwitchCompressor; QMutex savingEntryMutex; KisActionManager * actionManager() { return viewManager->actionManager(); } QTabBar* findTabBarHACK() { QObjectList objects = mdiArea->children(); Q_FOREACH (QObject *object, objects) { QTabBar *bar = qobject_cast(object); if (bar) { return bar; } } return 0; } }; KisMainWindow::KisMainWindow() : KXmlGuiWindow() , d(new Private(this)) { + auto rserver = KisResourceServerProvider::instance()->workspaceServer(false); + QSharedPointer adapter(new KoResourceServerAdapter(rserver)); + d->workspacemodel = new KoResourceModel(adapter, this); + connect(d->workspacemodel, &KoResourceModel::afterResourcesLayoutReset, this, [&]() { updateWindowMenu(); }); + KisConfig cfg; d->viewManager = new KisViewManager(this, actionCollection()); KConfigGroup group( KSharedConfig::openConfig(), "theme"); d->themeManager = new Digikam::ThemeManager(group.readEntry("Theme", "Krita dark"), this); setAcceptDrops(true); setStandardToolBarMenuEnabled(true); setTabPosition(Qt::AllDockWidgetAreas, QTabWidget::North); setDockNestingEnabled(true); qApp->setStartDragDistance(25); // 25 px is a distance that works well for Tablet and Mouse events #ifdef Q_OS_OSX setUnifiedTitleAndToolBarOnMac(true); #endif connect(this, SIGNAL(restoringDone()), this, SLOT(forceDockTabFonts())); connect(this, SIGNAL(themeChanged()), d->viewManager, SLOT(updateIcons())); connect(KisPart::instance(), SIGNAL(documentClosed(QString)), SLOT(updateWindowMenu())); connect(KisPart::instance(), SIGNAL(documentOpened(QString)), SLOT(updateWindowMenu())); connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), this, SLOT(configChanged())); actionCollection()->addAssociatedWidget(this); KoPluginLoader::instance()->load("Krita/ViewPlugin", "Type == 'Service' and ([X-Krita-Version] == 28)", KoPluginLoader::PluginsConfig(), d->viewManager); KoToolBoxFactory toolBoxFactory; QDockWidget *toolbox = createDockWidget(&toolBoxFactory); toolbox->setFeatures(QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable | QDockWidget::DockWidgetClosable); if (cfg.toolOptionsInDocker()) { ToolDockerFactory toolDockerFactory; d->toolOptionsDocker = qobject_cast(createDockWidget(&toolDockerFactory)); d->toolOptionsDocker->toggleViewAction()->setEnabled(true); } QMap dockwidgetActions; dockwidgetActions[toolbox->toggleViewAction()->text()] = toolbox->toggleViewAction(); Q_FOREACH (const QString & docker, KoDockRegistry::instance()->keys()) { KoDockFactoryBase *factory = KoDockRegistry::instance()->value(docker); QDockWidget *dw = createDockWidget(factory); dockwidgetActions[dw->toggleViewAction()->text()] = dw->toggleViewAction(); } if (d->toolOptionsDocker) { dockwidgetActions[d->toolOptionsDocker->toggleViewAction()->text()] = d->toolOptionsDocker->toggleViewAction(); } connect(KoToolManager::instance(), SIGNAL(toolOptionWidgetsChanged(KoCanvasController*, QList >)), this, SLOT(newOptionWidgets(KoCanvasController*, QList >))); Q_FOREACH (QString title, dockwidgetActions.keys()) { d->dockWidgetMenu->addAction(dockwidgetActions[title]); } Q_FOREACH (QDockWidget *wdg, dockWidgets()) { if ((wdg->features() & QDockWidget::DockWidgetClosable) == 0) { wdg->setVisible(true); } } Q_FOREACH (KoCanvasObserverBase* observer, canvasObservers()) { observer->setObservedCanvas(0); KisMainwindowObserver* mainwindowObserver = dynamic_cast(observer); if (mainwindowObserver) { mainwindowObserver->setMainWindow(d->viewManager); } } d->mdiArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); d->mdiArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); d->mdiArea->setTabPosition(QTabWidget::North); d->mdiArea->setTabsClosable(true); setCentralWidget(d->mdiArea); connect(d->mdiArea, SIGNAL(subWindowActivated(QMdiSubWindow*)), this, SLOT(subWindowActivated())); connect(d->windowMapper, SIGNAL(mapped(QWidget*)), this, SLOT(setActiveSubWindow(QWidget*))); connect(d->documentMapper, SIGNAL(mapped(QObject*)), this, SLOT(newView(QObject*))); createActions(); setAutoSaveSettings("krita", false); subWindowActivated(); updateWindowMenu(); if (isHelpMenuEnabled() && !d->helpMenu) { // workaround for KHelpMenu (or rather KAboutData::applicationData()) internally // not using the Q*Application metadata ATM, which results e.g. in the bugreport wizard // not having the app version preset // fixed hopefully in KF5 5.22.0, patch pending QGuiApplication *app = qApp; KAboutData aboutData(app->applicationName(), app->applicationDisplayName(), app->applicationVersion()); aboutData.setOrganizationDomain(app->organizationDomain().toUtf8()); d->helpMenu = new KHelpMenu(this, aboutData, false); // workaround-less version: // d->helpMenu = new KHelpMenu(this, QString()/*unused*/, false); // The difference between using KActionCollection->addAction() is that // these actions do not get tied to the MainWindow. What does this all do? KActionCollection *actions = d->viewManager->actionCollection(); QAction *helpContentsAction = d->helpMenu->action(KHelpMenu::menuHelpContents); QAction *whatsThisAction = d->helpMenu->action(KHelpMenu::menuWhatsThis); QAction *reportBugAction = d->helpMenu->action(KHelpMenu::menuReportBug); QAction *switchLanguageAction = d->helpMenu->action(KHelpMenu::menuSwitchLanguage); QAction *aboutAppAction = d->helpMenu->action(KHelpMenu::menuAboutApp); QAction *aboutKdeAction = d->helpMenu->action(KHelpMenu::menuAboutKDE); if (helpContentsAction) { actions->addAction(helpContentsAction->objectName(), helpContentsAction); } if (whatsThisAction) { actions->addAction(whatsThisAction->objectName(), whatsThisAction); } if (reportBugAction) { actions->addAction(reportBugAction->objectName(), reportBugAction); } if (switchLanguageAction) { actions->addAction(switchLanguageAction->objectName(), switchLanguageAction); } if (aboutAppAction) { actions->addAction(aboutAppAction->objectName(), aboutAppAction); } if (aboutKdeAction) { actions->addAction(aboutKdeAction->objectName(), aboutKdeAction); } connect(d->helpMenu, SIGNAL(showAboutApplication()), SLOT(showAboutApplication())); } // KDE' libs 4''s help contents action is broken outside kde, for some reason... We can handle it just as easily ourselves QAction *helpAction = actionCollection()->action("help_contents"); helpAction->disconnect(); connect(helpAction, SIGNAL(triggered()), this, SLOT(showManual())); #if 0 //check for colliding shortcuts QSet existingShortcuts; Q_FOREACH (QAction* action, actionCollection()->actions()) { if(action->shortcut() == QKeySequence(0)) { continue; } dbgKrita << "shortcut " << action->text() << " " << action->shortcut(); Q_ASSERT(!existingShortcuts.contains(action->shortcut())); existingShortcuts.insert(action->shortcut()); } #endif configChanged(); // If we have customized the toolbars, load that first setLocalXMLFile(KoResourcePaths::locateLocal("data", "krita.xmlgui")); setXMLFile(":/kxmlgui5/krita.xmlgui"); guiFactory()->addClient(this); // Create and plug toolbar list for Settings menu QList toolbarList; Q_FOREACH (QWidget* it, guiFactory()->containers("ToolBar")) { KToolBar * toolBar = ::qobject_cast(it); if (toolBar) { if (toolBar->objectName() == "BrushesAndStuff") { toolBar->setEnabled(false); } KToggleAction* act = new KToggleAction(i18n("Show %1 Toolbar", toolBar->windowTitle()), this); actionCollection()->addAction(toolBar->objectName().toUtf8(), act); act->setCheckedState(KGuiItem(i18n("Hide %1 Toolbar", toolBar->windowTitle()))); connect(act, SIGNAL(toggled(bool)), this, SLOT(slotToolbarToggled(bool))); act->setChecked(!toolBar->isHidden()); toolbarList.append(act); } else warnUI << "Toolbar list contains a " << it->metaObject()->className() << " which is not a toolbar!"; } plugActionList("toolbarlist", toolbarList); setToolbarList(toolbarList); applyToolBarLayout(); d->viewManager->updateGUI(); d->viewManager->updateIcons(); #ifdef Q_OS_WIN auto w = qApp->activeWindow(); if (w) QWindowsWindowFunctions::setHasBorderInFullScreen(w->windowHandle(), true); #endif QTimer::singleShot(1000, this, SLOT(checkSanity())); { using namespace std::placeholders; // For _1 placeholder std::function callback( std::bind(&KisMainWindow::switchTab, this, _1)); d->tabSwitchCompressor.reset( new KisSignalCompressorWithParam(500, callback, KisSignalCompressor::FIRST_INACTIVE)); } } void KisMainWindow::setNoCleanup(bool noCleanup) { d->noCleanup = noCleanup; } KisMainWindow::~KisMainWindow() { // Q_FOREACH (QAction *ac, actionCollection()->actions()) { // QAction *action = qobject_cast(ac); // if (action) { // dbgKrita << "", "").replace("", "") // << "iconText=" << action->iconText().replace("&", "&") // << "shortcut=" << action->shortcut(QAction::ActiveShortcut).toString() // << "defaultShortcut=" << action->shortcut(QAction::DefaultShortcut).toString() // << "isCheckable=" << QString((action->isChecked() ? "true" : "false")) // << "statusTip=" << action->statusTip() // << "/>" ; // } // else { // dbgKrita << "Got a QAction:" << ac->objectName(); // } // } // The doc and view might still exist (this is the case when closing the window) KisPart::instance()->removeMainWindow(this); if (d->noCleanup) return; delete d->viewManager; delete d; } void KisMainWindow::addView(KisView *view) { if (d->activeView == view) return; if (d->activeView) { d->activeView->disconnect(this); } showView(view); updateCaption(); emit restoringDone(); if (d->activeView) { connect(d->activeView, SIGNAL(titleModified(QString,bool)), SLOT(slotDocumentTitleModified(QString,bool))); } } void KisMainWindow::showView(KisView *imageView) { if (imageView && activeView() != imageView) { // XXX: find a better way to initialize this! imageView->setViewManager(d->viewManager); imageView->canvasBase()->setFavoriteResourceManager(d->viewManager->paintOpBox()->favoriteResourcesManager()); imageView->slotLoadingFinished(); QMdiSubWindow *subwin = d->mdiArea->addSubWindow(imageView); subwin->setAttribute(Qt::WA_DeleteOnClose, true); connect(subwin, SIGNAL(destroyed()), SLOT(updateWindowMenu())); KisConfig cfg; subwin->setOption(QMdiSubWindow::RubberBandMove, cfg.readEntry("mdi_rubberband", cfg.useOpenGL())); subwin->setOption(QMdiSubWindow::RubberBandResize, cfg.readEntry("mdi_rubberband", cfg.useOpenGL())); subwin->setWindowIcon(qApp->windowIcon()); /** * Hack alert! * * Here we explicitly request KoToolManager to emit all the tool * activation signals, to reinitialize the tool options docker. * * That is needed due to a design flaw we have in the * initialization procedure. The tool in the KoToolManager is * initialized in KisView::setViewManager() calls, which * happens early enough. During this call the tool manager * requests KoCanvasControllerWidget to emit the signal to * update the widgets in the tool docker. *But* at that moment * of time the view is not yet connected to the main window, * because it happens in KisViewManager::setCurrentView a bit * later. This fact makes the widgets updating signals be lost * and never reach the tool docker. * * So here we just explicitly call the tool activation stub. */ KoToolManager::instance()->initializeCurrentToolForCanvas(); if (d->mdiArea->subWindowList().size() == 1) { imageView->showMaximized(); } else { imageView->show(); } // No, no, no: do not try to call this _before_ the show() has // been called on the view; only when that has happened is the // opengl context active, and very bad things happen if we tell // the dockers to update themselves with a view if the opengl // context is not active. setActiveView(imageView); updateWindowMenu(); updateCaption(); } } void KisMainWindow::slotPreferences() { if (KisDlgPreferences::editPreferences()) { KisConfigNotifier::instance()->notifyConfigChanged(); // XXX: should this be changed for the views in other windows as well? Q_FOREACH (QPointer koview, KisPart::instance()->views()) { KisViewManager *view = qobject_cast(koview); if (view) { // Update the settings for all nodes -- they don't query // KisConfig directly because they need the settings during // compositing, and they don't connect to the config notifier // because nodes are not QObjects (because only one base class // can be a QObject). KisNode* node = dynamic_cast(view->image()->rootLayer().data()); node->updateSettings(); } } d->viewManager->showHideScrollbars(); } } void KisMainWindow::slotThemeChanged() { // save theme changes instantly KConfigGroup group( KSharedConfig::openConfig(), "theme"); group.writeEntry("Theme", d->themeManager->currentThemeName()); // reload action icons! Q_FOREACH (QAction *action, actionCollection()->actions()) { KisIconUtils::updateIcon(action); } emit themeChanged(); } void KisMainWindow::updateReloadFileAction(KisDocument *doc) { Q_UNUSED(doc); // d->reloadFile->setEnabled(doc && !doc->url().isEmpty()); } void KisMainWindow::setReadWrite(bool readwrite) { d->saveAction->setEnabled(readwrite); d->importFile->setEnabled(readwrite); d->readOnly = !readwrite; updateCaption(); } void KisMainWindow::addRecentURL(const QUrl &url) { dbgUI << "KisMainWindow::addRecentURL url=" << url.toDisplayString(); // Add entry to recent documents list // (call coming from KisDocument because it must work with cmd line, template dlg, file/open, etc.) if (!url.isEmpty()) { bool ok = true; if (url.isLocalFile()) { QString path = url.adjusted(QUrl::StripTrailingSlash).toLocalFile(); const QStringList tmpDirs = KoResourcePaths::resourceDirs("tmp"); for (QStringList::ConstIterator it = tmpDirs.begin() ; ok && it != tmpDirs.end() ; ++it) if (path.contains(*it)) ok = false; // it's in the tmp resource #ifdef HAVE_KIO if (ok) { KRecentDocument::add(QUrl::fromLocalFile(path)); } #endif } #ifdef HAVE_KIO else { KRecentDocument::add(url.adjusted(QUrl::StripTrailingSlash)); } #endif if (ok) { d->recentFiles->addUrl(url); } saveRecentFiles(); } } void KisMainWindow::saveRecentFiles() { // Save list of recent files KSharedConfigPtr config = KSharedConfig::openConfig(); d->recentFiles->saveEntries(config->group("RecentFiles")); config->sync(); // Tell all windows to reload their list, after saving // Doesn't work multi-process, but it's a start Q_FOREACH (KMainWindow* window, KMainWindow::memberList()) static_cast(window)->reloadRecentFileList(); } void KisMainWindow::reloadRecentFileList() { d->recentFiles->loadEntries( KSharedConfig::openConfig()->group("RecentFiles")); } void KisMainWindow::updateCaption() { if (!d->mdiArea->activeSubWindow()) { updateCaption(QString(), false); } else if (d->activeView && d->activeView->document()){ QString caption( d->activeView->document()->caption() ); if (d->readOnly) { caption += ' ' + i18n("(write protected)"); } d->activeView->setWindowTitle(caption); updateCaption(caption, d->activeView->document()->isModified()); if (!d->activeView->document()->url().fileName().isEmpty()) d->saveAction->setToolTip(i18n("Save as %1", d->activeView->document()->url().fileName())); else d->saveAction->setToolTip(i18n("Save")); } } void KisMainWindow::updateCaption(const QString & caption, bool mod) { dbgUI << "KisMainWindow::updateCaption(" << caption << "," << mod << ")"; #ifdef KRITA_ALPHA setCaption(QString("ALPHA %1: %2").arg(KRITA_ALPHA).arg(caption), mod); return; #endif #ifdef KRITA_BETA setCaption(QString("BETA %1: %2").arg(KRITA_BETA).arg(caption), mod); return; #endif #ifdef KRITA_RC setCaption(QString("RELEASE CANDIDATE %1: %2").arg(KRITA_RC).arg(caption), mod); return; #endif setCaption(caption, mod); } KisView *KisMainWindow::activeView() const { if (d->activeView) { return d->activeView; } return 0; } bool KisMainWindow::openDocument(const QUrl &url) { if (!QFile(url.toLocalFile()).exists()) { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("The file %1 does not exist.", url.url())); d->recentFiles->removeUrl(url); //remove the file from the recent-opened-file-list saveRecentFiles(); return false; } return openDocumentInternal(url); } bool KisMainWindow::openDocumentInternal(const QUrl &url, KisDocument *newdoc) { if (!url.isLocalFile()) { qDebug() << "KisMainWindow::openDocumentInternal. Not a local file:" << url; return false; } if (!newdoc) { newdoc = KisPart::instance()->createDocument(); } d->firstTime = true; connect(newdoc, SIGNAL(sigProgress(int)), this, SLOT(slotProgress(int))); connect(newdoc, SIGNAL(completed()), this, SLOT(slotLoadCompleted())); connect(newdoc, SIGNAL(canceled(const QString &)), this, SLOT(slotLoadCanceled(const QString &))); bool openRet = (!d->isImporting) ? newdoc->openUrl(url) : newdoc->importDocument(url); if (!openRet) { delete newdoc; return false; } KisPart::instance()->addDocument(newdoc); updateReloadFileAction(newdoc); if (!QFileInfo(url.toLocalFile()).isWritable()) { setReadWrite(false); } return true; } void KisMainWindow::addViewAndNotifyLoadingCompleted(KisDocument *document) { KisView *view = KisPart::instance()->createView(document, resourceManager(), actionCollection(), this); addView(view); emit guiLoadingFinished(); } QStringList KisMainWindow::showOpenFileDialog() { KoFileDialog dialog(this, KoFileDialog::ImportFiles, "OpenDocument"); dialog.setDefaultDir(QDesktopServices::storageLocation(QDesktopServices::PicturesLocation)); dialog.setMimeTypeFilters(KisImportExportManager::mimeFilter(KisImportExportManager::Import)); dialog.setCaption(d->isImporting ? i18n("Import Images") : i18n("Open Images")); return dialog.filenames(); } // Separate from openDocument to handle async loading (remote URLs) void KisMainWindow::slotLoadCompleted() { KisDocument *newdoc = qobject_cast(sender()); if (newdoc && newdoc->image()) { addViewAndNotifyLoadingCompleted(newdoc); disconnect(newdoc, SIGNAL(sigProgress(int)), this, SLOT(slotProgress(int))); disconnect(newdoc, SIGNAL(completed()), this, SLOT(slotLoadCompleted())); disconnect(newdoc, SIGNAL(canceled(const QString &)), this, SLOT(slotLoadCanceled(const QString &))); emit loadCompleted(); } } void KisMainWindow::slotLoadCanceled(const QString & errMsg) { dbgUI << "KisMainWindow::slotLoadCanceled"; if (!errMsg.isEmpty()) // empty when canceled by user QMessageBox::critical(this, i18nc("@title:window", "Krita"), errMsg); // ... can't delete the document, it's the one who emitted the signal... KisDocument* doc = qobject_cast(sender()); Q_ASSERT(doc); disconnect(doc, SIGNAL(sigProgress(int)), this, SLOT(slotProgress(int))); disconnect(doc, SIGNAL(completed()), this, SLOT(slotLoadCompleted())); disconnect(doc, SIGNAL(canceled(const QString &)), this, SLOT(slotLoadCanceled(const QString &))); } void KisMainWindow::slotSaveCanceled(const QString &errMsg) { dbgUI << "KisMainWindow::slotSaveCanceled"; if (!errMsg.isEmpty()) // empty when canceled by user QMessageBox::critical(this, i18nc("@title:window", "Krita"), errMsg); slotSaveCompleted(); } void KisMainWindow::slotSaveCompleted() { dbgUI << "KisMainWindow::slotSaveCompleted"; KisDocument* doc = qobject_cast(sender()); Q_ASSERT(doc); disconnect(doc, SIGNAL(sigProgress(int)), this, SLOT(slotProgress(int))); disconnect(doc, SIGNAL(completed()), this, SLOT(slotSaveCompleted())); disconnect(doc, SIGNAL(canceled(const QString &)), this, SLOT(slotSaveCanceled(const QString &))); if (d->deferredClosingEvent) { KXmlGuiWindow::closeEvent(d->deferredClosingEvent); } } bool KisMainWindow::hackIsSaving() const { StdLockableWrapper wrapper(&d->savingEntryMutex); std::unique_lock> l(wrapper, std::try_to_lock); return !l.owns_lock(); } bool KisMainWindow::saveDocument(KisDocument *document, bool saveas) { if (!document) { return true; } /** * Make sure that we cannot enter this method twice! * * The lower level functions may call processEvents() so * double-entry is quite possible to achieve. Here we try to lock * the mutex, and if it is failed, just cancel saving. */ StdLockableWrapper wrapper(&d->savingEntryMutex); std::unique_lock> l(wrapper, std::try_to_lock); if (!l.owns_lock()) return false; // no busy wait for saving because it is dangerous! KisDelayedSaveDialog dlg(document->image(), KisDelayedSaveDialog::SaveDialog, 0, this); dlg.blockIfImageIsBusy(); if (dlg.result() == KisDelayedSaveDialog::Rejected) { return false; } else if (dlg.result() == KisDelayedSaveDialog::Ignored) { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("You are saving a file while the image is " "still rendering. The saved file may be " "incomplete or corrupted.\n\n" "Please select a location where the original " "file will not be overridden!")); saveas = true; } bool reset_url; if (document->url().isEmpty()) { reset_url = true; saveas = true; } else { reset_url = false; } connect(document, SIGNAL(sigProgress(int)), this, SLOT(slotProgress(int))); connect(document, SIGNAL(completed()), this, SLOT(slotSaveCompleted())); connect(document, SIGNAL(canceled(const QString &)), this, SLOT(slotSaveCanceled(const QString &))); QUrl oldURL = document->url(); QString oldFile = document->localFilePath(); QByteArray _native_format = document->nativeFormatMimeType(); QByteArray oldOutputFormat = document->outputMimeType(); QUrl suggestedURL = document->url(); QStringList mimeFilter; mimeFilter = KisImportExportManager::mimeFilter(KisImportExportManager::Export); if (!mimeFilter.contains(oldOutputFormat) && !d->isExporting) { dbgUI << "KisMainWindow::saveDocument no export filter for" << oldOutputFormat; // --- don't setOutputMimeType in case the user cancels the Save As // dialog and then tries to just plain Save --- // suggest a different filename extension (yes, we fortunately don't all live in a world of magic :)) QString suggestedFilename = suggestedURL.fileName(); if (!suggestedFilename.isEmpty()) { // ".kra" looks strange for a name int c = suggestedFilename.lastIndexOf('.'); const QString ext = KisMimeDatabase::suffixesForMimeType(_native_format).first(); if (!ext.isEmpty()) { if (c < 0) suggestedFilename = suggestedFilename + "." + ext; else suggestedFilename = suggestedFilename.left(c) + "." + ext; } else { // current filename extension wrong anyway if (c > 0) { // this assumes that a . signifies an extension, not just a . suggestedFilename = suggestedFilename.left(c); } } suggestedURL = suggestedURL.adjusted(QUrl::RemoveFilename); suggestedURL.setPath(suggestedURL.path() + suggestedFilename); } // force the user to choose outputMimeType saveas = true; } bool ret = false; if (document->url().isEmpty() || saveas) { // if you're just File/Save As'ing to change filter options you // don't want to be reminded about overwriting files etc. bool justChangingFilterOptions = false; KoFileDialog dialog(this, KoFileDialog::SaveFile, "SaveAs"); dialog.setCaption(i18n("untitled")); if (d->isExporting && !d->lastExportUrl.isEmpty()) { dialog.setDefaultDir(d->lastExportUrl.toLocalFile()); } else { dialog.setDefaultDir(suggestedURL.toLocalFile()); } // Default to all supported file types if user is exporting, otherwise use Krita default dialog.setMimeTypeFilters(mimeFilter, QString(_native_format)); QUrl newURL = QUrl::fromUserInput(dialog.filename()); if (newURL.isLocalFile()) { QString fn = newURL.toLocalFile(); if (QFileInfo(fn).completeSuffix().isEmpty()) { fn.append(KisMimeDatabase::suffixesForMimeType(_native_format).first()); newURL = QUrl::fromLocalFile(fn); } } if (document->documentInfo()->aboutInfo("title") == i18n("Unnamed")) { QString fn = newURL.toLocalFile(); QFileInfo info(fn); document->documentInfo()->setAboutInfo("title", info.baseName()); } QByteArray outputFormat = _native_format; QString outputFormatString = KisMimeDatabase::mimeTypeForFile(newURL.toLocalFile()); outputFormat = outputFormatString.toLatin1(); if (!d->isExporting) { justChangingFilterOptions = (newURL == document->url()) && (outputFormat == document->mimeType()); } else { justChangingFilterOptions = (newURL == d->lastExportUrl) && (outputFormat == d->lastExportedFormat); } bool bOk = true; if (newURL.isEmpty()) { bOk = false; } if (bOk) { bool wantToSave = true; // don't change this line unless you know what you're doing :) if (!justChangingFilterOptions) { if (!document->isNativeFormat(outputFormat)) wantToSave = true; } if (wantToSave) { // // Note: // If the user is stupid enough to Export to the current URL, // we do _not_ change this operation into a Save As. Reasons // follow: // // 1. A check like "d->isExporting && oldURL == newURL" // doesn't _always_ work on case-insensitive filesystems // and inconsistent behaviour is bad. // 2. It is probably not a good idea to change document->mimeType // and friends because the next time the user File/Save's, // (not Save As) they won't be expecting that they are // using their File/Export settings // // As a bad side-effect of this, the modified flag will not // be updated and it is possible that what is currently on // their screen is not what is stored on disk (through loss // of formatting). But if you are dumb enough to change // mimetype but not the filename, then arguably, _you_ are // the "bug" :) // // - Clarence // document->setOutputMimeType(outputFormat); if (!d->isExporting) { // Save As ret = document->saveAs(newURL); if (ret) { dbgUI << "Successful Save As!"; addRecentURL(newURL); setReadWrite(true); } else { dbgUI << "Failed Save As!"; document->setUrl(oldURL); document->setLocalFilePath(oldFile); document->setOutputMimeType(oldOutputFormat); } } else { // Export ret = document->exportDocument(newURL); if (ret) { // a few file dialog convenience things d->lastExportUrl = newURL; d->lastExportedFormat = outputFormat; } // always restore output format document->setOutputMimeType(oldOutputFormat); } } // if (wantToSave) { else ret = false; } // if (bOk) { else ret = false; } else { // saving // be sure document has the correct outputMimeType! if (d->isExporting || document->isModified()) { ret = document->save(); } if (!ret) { dbgUI << "Failed Save!"; document->setUrl(oldURL); document->setLocalFilePath(oldFile); } } if (!ret && reset_url) document->resetURL(); //clean the suggested filename as the save dialog was rejected updateReloadFileAction(document); updateCaption(); return ret; } void KisMainWindow::undo() { if (activeView()) { activeView()->undoAction()->trigger(); d->undo->setText(activeView()->undoAction()->text()); } } void KisMainWindow::redo() { if (activeView()) { activeView()->redoAction()->trigger(); d->redo->setText(activeView()->redoAction()->text()); } } void KisMainWindow::closeEvent(QCloseEvent *e) { d->mdiArea->closeAllSubWindows(); QAction *action= d->viewManager->actionCollection()->action("view_show_canvas_only"); if ((action) && (action->isChecked())) { action->setChecked(false); } KConfigGroup cfg( KSharedConfig::openConfig(), "MainWindow"); cfg.writeEntry("ko_geometry", saveGeometry().toBase64()); cfg.writeEntry("ko_windowstate", saveState().toBase64()); { KConfigGroup group( KSharedConfig::openConfig(), "theme"); group.writeEntry("Theme", d->themeManager->currentThemeName()); } QList childrenList = d->mdiArea->subWindowList(); if (childrenList.isEmpty()) { d->deferredClosingEvent = e; if (!d->dockerStateBeforeHiding.isEmpty()) { restoreState(d->dockerStateBeforeHiding); } statusBar()->setVisible(true); menuBar()->setVisible(true); saveWindowSettings(); if (d->noCleanup) return; if (!d->dockWidgetVisibilityMap.isEmpty()) { // re-enable dockers for persistency Q_FOREACH (QDockWidget* dockWidget, d->dockWidgetsMap) dockWidget->setVisible(d->dockWidgetVisibilityMap.value(dockWidget)); } } else { e->setAccepted(false); } } void KisMainWindow::saveWindowSettings() { KSharedConfigPtr config = KSharedConfig::openConfig(); if (d->windowSizeDirty ) { dbgUI << "KisMainWindow::saveWindowSettings"; KConfigGroup group = config->group("MainWindow"); KWindowConfig::saveWindowSize(windowHandle(), group); config->sync(); d->windowSizeDirty = false; } if (!d->activeView || d->activeView->document()) { // Save toolbar position into the config file of the app, under the doc's component name KConfigGroup group = KSharedConfig::openConfig()->group("krita"); saveMainWindowSettings(group); // Save collapsable state of dock widgets for (QMap::const_iterator i = d->dockWidgetsMap.constBegin(); i != d->dockWidgetsMap.constEnd(); ++i) { if (i.value()->widget()) { KConfigGroup dockGroup = group.group(QString("DockWidget ") + i.key()); dockGroup.writeEntry("Collapsed", i.value()->widget()->isHidden()); dockGroup.writeEntry("Locked", i.value()->property("Locked").toBool()); dockGroup.writeEntry("DockArea", (int) dockWidgetArea(i.value())); dockGroup.writeEntry("xPosition", (int) i.value()->widget()->x()); dockGroup.writeEntry("yPosition", (int) i.value()->widget()->y()); dockGroup.writeEntry("width", (int) i.value()->widget()->width()); dockGroup.writeEntry("height", (int) i.value()->widget()->height()); } } } KSharedConfig::openConfig()->sync(); resetAutoSaveSettings(); // Don't let KMainWindow override the good stuff we wrote down } void KisMainWindow::resizeEvent(QResizeEvent * e) { d->windowSizeDirty = true; KXmlGuiWindow::resizeEvent(e); } void KisMainWindow::setActiveView(KisView* view) { d->activeView = view; updateCaption(); actionCollection()->action("edit_undo")->setText(activeView()->undoAction()->text()); actionCollection()->action("edit_redo")->setText(activeView()->redoAction()->text()); d->viewManager->setCurrentView(view); } void KisMainWindow::dragEnterEvent(QDragEnterEvent *event) { if (event->mimeData()->hasUrls() || event->mimeData()->hasFormat("application/x-krita-node") || event->mimeData()->hasFormat("application/x-qt-image")) { event->accept(); } } void KisMainWindow::dropEvent(QDropEvent *event) { if (event->mimeData()->hasUrls() && event->mimeData()->urls().size() > 0) { Q_FOREACH (const QUrl &url, event->mimeData()->urls()) { openDocument(url); } } } void KisMainWindow::dragMoveEvent(QDragMoveEvent * event) { QTabBar *tabBar = d->findTabBarHACK(); if (!tabBar && d->mdiArea->viewMode() == QMdiArea::TabbedView) { qWarning() << "WARNING!!! Cannot find QTabBar in the main window! Looks like Qt has changed behavior. Drag & Drop between multiple tabs might not work properly (tabs will not switch automatically)!"; } if (tabBar && tabBar->isVisible()) { QPoint pos = tabBar->mapFromGlobal(mapToGlobal(event->pos())); if (tabBar->rect().contains(pos)) { const int tabIndex = tabBar->tabAt(pos); if (tabIndex >= 0 && tabBar->currentIndex() != tabIndex) { d->tabSwitchCompressor->start(tabIndex); } } else if (d->tabSwitchCompressor->isActive()) { d->tabSwitchCompressor->stop(); } } } void KisMainWindow::dragLeaveEvent(QDragLeaveEvent * /*event*/) { if (d->tabSwitchCompressor->isActive()) { d->tabSwitchCompressor->stop(); } } void KisMainWindow::switchTab(int index) { QTabBar *tabBar = d->findTabBarHACK(); if (!tabBar) return; tabBar->setCurrentIndex(index); } void KisMainWindow::slotFileNew() { const QStringList mimeFilter = KisImportExportManager::mimeFilter(KisImportExportManager::Import); KisOpenPane *startupWidget = new KisOpenPane(this, mimeFilter, QStringLiteral("templates/")); startupWidget->setWindowModality(Qt::WindowModal); KisConfig cfg; int w = cfg.defImageWidth(); int h = cfg.defImageHeight(); const double resolution = cfg.defImageResolution(); const QString colorModel = cfg.defColorModel(); const QString colorDepth = cfg.defaultColorDepth(); const QString colorProfile = cfg.defColorProfile(); CustomDocumentWidgetItem item; item.widget = new KisCustomImageWidget(startupWidget, w, h, resolution, colorModel, colorDepth, colorProfile, i18n("Unnamed")); item.icon = "document-new"; startupWidget->addCustomDocumentWidget(item.widget, item.title, item.icon); QSize sz = KisClipboard::instance()->clipSize(); if (sz.isValid() && sz.width() != 0 && sz.height() != 0) { w = sz.width(); h = sz.height(); } item.widget = new KisImageFromClipboard(startupWidget, w, h, resolution, colorModel, colorDepth, colorProfile, i18n("Unnamed")); item.title = i18n("Create from Clipboard"); item.icon = "tab-new"; startupWidget->addCustomDocumentWidget(item.widget, item.title, item.icon); // calls deleteLater connect(startupWidget, SIGNAL(documentSelected(KisDocument*)), KisPart::instance(), SLOT(startCustomDocument(KisDocument*))); // calls deleteLater connect(startupWidget, SIGNAL(openTemplate(const QUrl&)), KisPart::instance(), SLOT(openTemplate(const QUrl&))); startupWidget->exec(); // Cancel calls deleteLater... } void KisMainWindow::slotFileOpen() { QStringList urls = showOpenFileDialog(); if (urls.isEmpty()) return; Q_FOREACH (const QString& url, urls) { if (!url.isEmpty()) { bool res = openDocument(QUrl::fromLocalFile(url)); if (!res) { warnKrita << "Loading" << url << "failed"; } } } } void KisMainWindow::slotFileOpenRecent(const QUrl &url) { (void) openDocument(QUrl::fromLocalFile(url.toLocalFile())); } void KisMainWindow::slotFileSave() { if (saveDocument(d->activeView->document())) { emit documentSaved(); } } void KisMainWindow::slotFileSaveAs() { if (saveDocument(d->activeView->document(), true)) { emit documentSaved(); } } KoCanvasResourceManager *KisMainWindow::resourceManager() const { return d->viewManager->resourceProvider()->resourceManager(); } int KisMainWindow::viewCount() const { return d->mdiArea->subWindowList().size(); } bool KisMainWindow::restoreWorkspace(const QByteArray &state) { QByteArray oldState = saveState(); const bool showTitlebars = KisConfig().showDockerTitleBars(); // needed because otherwise the layout isn't correctly restored in some situations Q_FOREACH (QDockWidget *dock, dockWidgets()) { dock->hide(); dock->titleBarWidget()->setVisible(showTitlebars); } bool success = KXmlGuiWindow::restoreState(state); if (!success) { KXmlGuiWindow::restoreState(oldState); Q_FOREACH (QDockWidget *dock, dockWidgets()) { if (dock->titleBarWidget()) { dock->titleBarWidget()->setVisible(showTitlebars || dock->isFloating()); } } return false; } Q_FOREACH (QDockWidget *dock, dockWidgets()) { if (dock->titleBarWidget()) { const bool isCollapsed = (dock->widget() && dock->widget()->isHidden()) || !dock->widget(); dock->titleBarWidget()->setVisible(showTitlebars || (dock->isFloating() && isCollapsed)); } } return success; } KisViewManager *KisMainWindow::viewManager() const { return d->viewManager; } void KisMainWindow::slotDocumentInfo() { if (!d->activeView->document()) return; KoDocumentInfo *docInfo = d->activeView->document()->documentInfo(); if (!docInfo) return; KoDocumentInfoDlg *dlg = d->activeView->document()->createDocumentInfoDialog(this, docInfo); if (dlg->exec()) { if (dlg->isDocumentSaved()) { d->activeView->document()->setModified(false); } else { d->activeView->document()->setModified(true); } d->activeView->document()->setTitleModified(); } delete dlg; } bool KisMainWindow::slotFileCloseAll() { Q_FOREACH (QMdiSubWindow *subwin, d->mdiArea->subWindowList()) { if (subwin) { if(!subwin->close()) return false; } } updateCaption(); return true; } void KisMainWindow::slotFileQuit() { if(!slotFileCloseAll()) return; close(); Q_FOREACH (QPointer mainWin, KisPart::instance()->mainWindows()) { if (mainWin != this) { if(!mainWin->slotFileCloseAll()) return; mainWin->close(); } } } void KisMainWindow::slotFilePrint() { if (!activeView()) return; KisPrintJob *printJob = activeView()->createPrintJob(); if (printJob == 0) return; applyDefaultSettings(printJob->printer()); QPrintDialog *printDialog = activeView()->createPrintDialog( printJob, this ); if (printDialog && printDialog->exec() == QDialog::Accepted) { printJob->printer().setPageMargins(0.0, 0.0, 0.0, 0.0, QPrinter::Point); printJob->printer().setPaperSize(QSizeF(activeView()->image()->width() / (72.0 * activeView()->image()->xRes()), activeView()->image()->height()/ (72.0 * activeView()->image()->yRes())), QPrinter::Inch); printJob->startPrinting(KisPrintJob::DeleteWhenDone); } else { delete printJob; } delete printDialog; } void KisMainWindow::slotFilePrintPreview() { if (!activeView()) return; KisPrintJob *printJob = activeView()->createPrintJob(); if (printJob == 0) return; /* Sets the startPrinting() slot to be blocking. The Qt print-preview dialog requires the printing to be completely blocking and only return when the full document has been printed. By default the KisPrintingDialog is non-blocking and multithreading, setting blocking to true will allow it to be used in the preview dialog */ printJob->setProperty("blocking", true); QPrintPreviewDialog *preview = new QPrintPreviewDialog(&printJob->printer(), this); printJob->setParent(preview); // will take care of deleting the job connect(preview, SIGNAL(paintRequested(QPrinter*)), printJob, SLOT(startPrinting())); preview->exec(); delete preview; } KisPrintJob* KisMainWindow::exportToPdf(QString pdfFileName) { if (!activeView()) return 0; if (!activeView()->document()) return 0; KoPageLayout pageLayout; pageLayout.width = 0; pageLayout.height = 0; pageLayout.topMargin = 0; pageLayout.bottomMargin = 0; pageLayout.leftMargin = 0; pageLayout.rightMargin = 0; if (pdfFileName.isEmpty()) { KConfigGroup group = KSharedConfig::openConfig()->group("File Dialogs"); QString defaultDir = group.readEntry("SavePdfDialog"); if (defaultDir.isEmpty()) defaultDir = QDesktopServices::storageLocation(QDesktopServices::DocumentsLocation); QUrl startUrl = QUrl::fromLocalFile(defaultDir); KisDocument* pDoc = d->activeView->document(); /** if document has a file name, take file name and replace extension with .pdf */ if (pDoc && pDoc->url().isValid()) { startUrl = pDoc->url(); QString fileName = startUrl.toLocalFile(); fileName = fileName.replace( QRegExp( "\\.\\w{2,5}$", Qt::CaseInsensitive ), ".pdf" ); startUrl = startUrl.adjusted(QUrl::RemoveFilename); startUrl.setPath(startUrl.path() + fileName ); } QPointer layoutDlg(new KoPageLayoutDialog(this, pageLayout)); layoutDlg->setWindowModality(Qt::WindowModal); if (layoutDlg->exec() != QDialog::Accepted || !layoutDlg) { delete layoutDlg; return 0; } pageLayout = layoutDlg->pageLayout(); delete layoutDlg; KoFileDialog dialog(this, KoFileDialog::SaveFile, "OpenDocument"); dialog.setCaption(i18n("Export as PDF")); dialog.setDefaultDir(startUrl.toLocalFile()); dialog.setMimeTypeFilters(QStringList() << "application/pdf"); QUrl url = QUrl::fromUserInput(dialog.filename()); pdfFileName = url.toLocalFile(); if (pdfFileName.isEmpty()) return 0; } KisPrintJob *printJob = activeView()->createPrintJob(); if (printJob == 0) return 0; if (isHidden()) { printJob->setProperty("noprogressdialog", true); } applyDefaultSettings(printJob->printer()); // TODO for remote files we have to first save locally and then upload. printJob->printer().setOutputFileName(pdfFileName); printJob->printer().setDocName(pdfFileName); printJob->printer().setColorMode(QPrinter::Color); if (pageLayout.format == KoPageFormat::CustomSize) { printJob->printer().setPaperSize(QSizeF(pageLayout.width, pageLayout.height), QPrinter::Millimeter); } else { printJob->printer().setPaperSize(KoPageFormat::printerPageSize(pageLayout.format)); } printJob->printer().setPageMargins(pageLayout.leftMargin, pageLayout.topMargin, pageLayout.rightMargin, pageLayout.bottomMargin, QPrinter::Millimeter); switch (pageLayout.orientation) { case KoPageFormat::Portrait: printJob->printer().setOrientation(QPrinter::Portrait); break; case KoPageFormat::Landscape: printJob->printer().setOrientation(QPrinter::Landscape); break; } //before printing check if the printer can handle printing if (!printJob->canPrint()) { QMessageBox::critical(this, i18nc("@title:window", "Krita"), i18n("Cannot export to the specified file")); } printJob->startPrinting(KisPrintJob::DeleteWhenDone); return printJob; } void KisMainWindow::importAnimation() { if (!activeView()) return; KisDocument *document = activeView()->document(); if (!document) return; KisDlgImportImageSequence dlg(this, document); if (dlg.exec() == QDialog::Accepted) { QStringList files = dlg.files(); int firstFrame = dlg.firstFrame(); int step = dlg.step(); document->setFileProgressProxy(); document->setFileProgressUpdater(i18n("Import frames")); KisAnimationImporter importer(document); KisImportExportFilter::ConversionStatus status = importer.import(files, firstFrame, step); document->clearFileProgressUpdater(); document->clearFileProgressProxy(); if (status != KisImportExportFilter::OK && status != KisImportExportFilter::InternalError) { QString msg = KisImportExportFilter::conversionStatusString(status); if (!msg.isEmpty()) QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Could not finish import animation:\n%1", msg)); } activeView()->canvasBase()->refetchDataFromImage(); } } void KisMainWindow::slotConfigureToolbars() { KConfigGroup group = KSharedConfig::openConfig()->group("krita"); saveMainWindowSettings(group); KEditToolBar edit(factory(), this); connect(&edit, SIGNAL(newToolBarConfig()), this, SLOT(slotNewToolbarConfig())); (void) edit.exec(); applyToolBarLayout(); } void KisMainWindow::slotNewToolbarConfig() { applyMainWindowSettings(KSharedConfig::openConfig()->group("krita")); KXMLGUIFactory *factory = guiFactory(); Q_UNUSED(factory); // Check if there's an active view if (!d->activeView) return; plugActionList("toolbarlist", d->toolbarList); applyToolBarLayout(); } void KisMainWindow::slotToolbarToggled(bool toggle) { //dbgUI <<"KisMainWindow::slotToolbarToggled" << sender()->name() <<" toggle=" << true; // The action (sender) and the toolbar have the same name KToolBar * bar = toolBar(sender()->objectName()); if (bar) { if (toggle) { bar->show(); } else { bar->hide(); } if (d->activeView && d->activeView->document()) { KConfigGroup group = KSharedConfig::openConfig()->group("krita"); saveMainWindowSettings(group); } } else warnUI << "slotToolbarToggled : Toolbar " << sender()->objectName() << " not found!"; } void KisMainWindow::viewFullscreen(bool fullScreen) { KisConfig cfg; cfg.setFullscreenMode(fullScreen); if (fullScreen) { setWindowState(windowState() | Qt::WindowFullScreen); // set } else { setWindowState(windowState() & ~Qt::WindowFullScreen); // reset } } void KisMainWindow::slotProgress(int value) { qApp->processEvents(); StdLockableWrapper wrapper(&d->progressMutex); std::unique_lock> l(wrapper, std::try_to_lock); if (!l.owns_lock()) return; dbgUI << "KisMainWindow::slotProgress" << value; if (value <= -1 || value >= 100) { if (d->progress) { statusBar()->removeWidget(d->progress); delete d->progress; d->progress = 0; disconnect(d->progressCancel, SIGNAL(clicked()), this, SLOT(slotProgressCanceled())); statusBar()->removeWidget(d->progressCancel); delete d->progressCancel; d->progressCancel = 0; } d->firstTime = true; return; } if (d->firstTime || !d->progress) { // The statusbar might not even be created yet. // So check for that first, and create it if necessary QStatusBar *bar = findChild(); if (!bar) { statusBar()->show(); QApplication::sendPostedEvents(this, QEvent::ChildAdded); } if (d->progress) { statusBar()->removeWidget(d->progress); delete d->progress; d->progress = 0; disconnect(d->progressCancel, SIGNAL(clicked()), this, SLOT(slotProgressCanceled())); statusBar()->removeWidget(d->progressCancel); delete d->progressCancel; d->progress = 0; } d->progressCancel = new QToolButton(statusBar()); d->progressCancel->setMaximumHeight(statusBar()->fontMetrics().height()); d->progressCancel->setIcon(KisIconUtils::loadIcon("process-stop")); statusBar()->addPermanentWidget(d->progressCancel); d->progress = new QProgressBar(statusBar()); d->progress->setMaximumHeight(statusBar()->fontMetrics().height()); d->progress->setRange(0, 100); statusBar()->addPermanentWidget(d->progress); connect(d->progressCancel, SIGNAL(clicked()), this, SLOT(slotProgressCanceled())); d->progress->show(); d->progressCancel->show(); d->firstTime = false; } if (!d->progress.isNull()) { d->progress->setValue(value); } qApp->processEvents(); } void KisMainWindow::slotProgressCanceled() { emit sigProgressCanceled(); } void KisMainWindow::setMaxRecentItems(uint _number) { d->recentFiles->setMaxItems(_number); } void KisMainWindow::slotReloadFile() { KisDocument* document = d->activeView->document(); if (!document || document->url().isEmpty()) return; if (document->isModified()) { bool ok = QMessageBox::question(this, i18nc("@title:window", "Krita"), i18n("You will lose all changes made since your last save\n" "Do you want to continue?"), QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes) == QMessageBox::Yes; if (!ok) return; } QUrl url = document->url(); saveWindowSettings(); if (!document->reload()) { QMessageBox::critical(this, i18nc("@title:window", "Krita"), i18n("Error: Could not reload this document")); } return; } void KisMainWindow::slotImportFile() { dbgUI << "slotImportFile()"; d->isImporting = true; slotFileOpen(); d->isImporting = false; } void KisMainWindow::slotExportFile() { dbgUI << "slotExportFile()"; d->isExporting = true; slotFileSaveAs(); d->isExporting = false; } QDockWidget* KisMainWindow::createDockWidget(KoDockFactoryBase* factory) { QDockWidget* dockWidget = 0; if (!d->dockWidgetsMap.contains(factory->id())) { dockWidget = factory->createDockWidget(); // It is quite possible that a dock factory cannot create the dock; don't // do anything in that case. if (!dockWidget) { warnKrita << "Could not create docker for" << factory->id(); return 0; } KoDockWidgetTitleBar *titleBar = dynamic_cast(dockWidget->titleBarWidget()); // Check if the dock widget is supposed to be collapsable if (!dockWidget->titleBarWidget()) { titleBar = new KoDockWidgetTitleBar(dockWidget); dockWidget->setTitleBarWidget(titleBar); titleBar->setCollapsable(factory->isCollapsable()); } titleBar->setFont(KoDockRegistry::dockFont()); dockWidget->setObjectName(factory->id()); dockWidget->setParent(this); if (dockWidget->widget() && dockWidget->widget()->layout()) dockWidget->widget()->layout()->setContentsMargins(1, 1, 1, 1); Qt::DockWidgetArea side = Qt::RightDockWidgetArea; bool visible = true; switch (factory->defaultDockPosition()) { case KoDockFactoryBase::DockTornOff: dockWidget->setFloating(true); // position nicely? break; case KoDockFactoryBase::DockTop: side = Qt::TopDockWidgetArea; break; case KoDockFactoryBase::DockLeft: side = Qt::LeftDockWidgetArea; break; case KoDockFactoryBase::DockBottom: side = Qt::BottomDockWidgetArea; break; case KoDockFactoryBase::DockRight: side = Qt::RightDockWidgetArea; break; case KoDockFactoryBase::DockMinimized: default: side = Qt::RightDockWidgetArea; visible = false; } KConfigGroup group = KSharedConfig::openConfig()->group("krita").group("DockWidget " + factory->id()); side = static_cast(group.readEntry("DockArea", static_cast(side))); if (side == Qt::NoDockWidgetArea) side = Qt::RightDockWidgetArea; addDockWidget(side, dockWidget); if (!visible) { dockWidget->hide(); } bool collapsed = factory->defaultCollapsed(); bool locked = false; group = KSharedConfig::openConfig()->group("krita").group("DockWidget " + factory->id()); collapsed = group.readEntry("Collapsed", collapsed); locked = group.readEntry("Locked", locked); //dbgKrita << "docker" << factory->id() << dockWidget << "collapsed" << collapsed << "locked" << locked << "titlebar" << titleBar; if (titleBar && collapsed) titleBar->setCollapsed(true); if (titleBar && locked) titleBar->setLocked(true); d->dockWidgetsMap.insert(factory->id(), dockWidget); } else { dockWidget = d->dockWidgetsMap[factory->id()]; } #ifdef Q_OS_OSX dockWidget->setAttribute(Qt::WA_MacSmallSize, true); #endif dockWidget->setFont(KoDockRegistry::dockFont()); connect(dockWidget, SIGNAL(dockLocationChanged(Qt::DockWidgetArea)), this, SLOT(forceDockTabFonts())); return dockWidget; } void KisMainWindow::forceDockTabFonts() { Q_FOREACH (QObject *child, children()) { if (child->inherits("QTabBar")) { ((QTabBar *)child)->setFont(KoDockRegistry::dockFont()); } } } QList KisMainWindow::dockWidgets() const { return d->dockWidgetsMap.values(); } QDockWidget* KisMainWindow::dockWidget(const QString &id) { if (!d->dockWidgetsMap.contains(id)) return 0; return d->dockWidgetsMap[id]; } QList KisMainWindow::canvasObservers() const { QList observers; Q_FOREACH (QDockWidget *docker, dockWidgets()) { KoCanvasObserverBase *observer = dynamic_cast(docker); if (observer) { observers << observer; } else { warnKrita << docker << "is not a canvas observer"; } } return observers; } void KisMainWindow::toggleDockersVisibility(bool visible) { if (!visible) { d->dockerStateBeforeHiding = saveState(); Q_FOREACH (QObject* widget, children()) { if (widget->inherits("QDockWidget")) { QDockWidget* dw = static_cast(widget); if (dw->isVisible()) { dw->hide(); } } } } else { restoreState(d->dockerStateBeforeHiding); } } void KisMainWindow::setToolbarList(QList toolbarList) { qDeleteAll(d->toolbarList); d->toolbarList = toolbarList; } void KisMainWindow::slotDocumentTitleModified(const QString &caption, bool mod) { updateCaption(); updateCaption(caption, mod); updateReloadFileAction(d->activeView ? d->activeView->document() : 0); } void KisMainWindow::subWindowActivated() { bool enabled = (activeKisView() != 0); d->mdiCascade->setEnabled(enabled); d->mdiNextWindow->setEnabled(enabled); d->mdiPreviousWindow->setEnabled(enabled); d->mdiTile->setEnabled(enabled); d->close->setEnabled(enabled); d->closeAll->setEnabled(enabled); setActiveSubWindow(d->mdiArea->activeSubWindow()); Q_FOREACH (QToolBar *tb, toolBars()) { if (tb->objectName() == "BrushesAndStuff") { tb->setEnabled(enabled); } } updateCaption(); d->actionManager()->updateGUI(); } void KisMainWindow::updateWindowMenu() { QMenu *menu = d->windowMenu->menu(); menu->clear(); menu->addAction(d->newWindow); menu->addAction(d->documentMenu); QMenu *docMenu = d->documentMenu->menu(); docMenu->clear(); Q_FOREACH (QPointer doc, KisPart::instance()->documents()) { if (doc) { QString title = doc->url().toDisplayString(); if (title.isEmpty() && doc->image()) { title = doc->image()->objectName(); } QAction *action = docMenu->addAction(title); action->setIcon(qApp->windowIcon()); connect(action, SIGNAL(triggered()), d->documentMapper, SLOT(map())); d->documentMapper->setMapping(action, doc); } } + menu->addAction(d->workspaceMenu); + QMenu *workspaceMenu = d->workspaceMenu->menu(); + workspaceMenu->clear(); + + auto workspaces = KisResourceServerProvider::instance()->workspaceServer(false)->resources(); + auto m_this = this; + for (auto &w : workspaces) { + auto action = workspaceMenu->addAction(w->name()); + auto ds = w->dockerState(); + connect(action, &QAction::triggered, this, [=]() { m_this->restoreWorkspace(ds); }); + } + workspaceMenu->addSeparator(); + connect(workspaceMenu->addAction(i18nc("@action:inmenu", "&Import Workspace...")), + &QAction::triggered, + this, + [&]() { + QString extensions = d->workspacemodel->extensions(); + QStringList mimeTypes; + for(const QString &suffix : extensions.split(":")) { + mimeTypes << KisMimeDatabase::mimeTypeForSuffix(suffix); + } + + KoFileDialog dialog(0, KoFileDialog::OpenFile, "OpenDocument"); + dialog.setMimeTypeFilters(mimeTypes); + dialog.setCaption(i18nc("@title:window", "Choose File to Add")); + QString filename = dialog.filename(); + + d->workspacemodel->importResourceFile(filename); + }); + + connect(workspaceMenu->addAction(i18nc("@action:inmenu", "&New Workspace...")), + &QAction::triggered, + [=]() { + QString name = QInputDialog::getText(this, i18nc("@title:window", "New Workspace..."), + i18nc("@label:textbox", "Name:")); + if (name.isEmpty()) return; + auto rserver = KisResourceServerProvider::instance()->workspaceServer(); + + KisWorkspaceResource* workspace = new KisWorkspaceResource(""); + workspace->setDockerState(m_this->saveState()); + d->viewManager->resourceProvider()->notifySavingWorkspace(workspace); + workspace->setValid(true); + QString saveLocation = rserver->saveLocation(); + + bool newName = false; + if(name.isEmpty()) { + newName = true; + name = i18n("Workspace"); + } + QFileInfo fileInfo(saveLocation + name + workspace->defaultFileExtension()); + + int i = 1; + while (fileInfo.exists()) { + fileInfo.setFile(saveLocation + name + QString("%1").arg(i) + workspace->defaultFileExtension()); + i++; + } + workspace->setFilename(fileInfo.filePath()); + if(newName) { + name = i18n("Workspace %1", i); + } + workspace->setName(name); + rserver->addResource(workspace); + }); + + // TODO: What to do about delete? +// workspaceMenu->addAction(i18nc("@action:inmenu", "&Delete Workspace...")); + menu->addSeparator(); menu->addAction(d->close); menu->addAction(d->closeAll); if (d->mdiArea->viewMode() == QMdiArea::SubWindowView) { menu->addSeparator(); menu->addAction(d->mdiTile); menu->addAction(d->mdiCascade); } menu->addSeparator(); menu->addAction(d->mdiNextWindow); menu->addAction(d->mdiPreviousWindow); menu->addSeparator(); QList windows = d->mdiArea->subWindowList(); for (int i = 0; i < windows.size(); ++i) { QPointerchild = qobject_cast(windows.at(i)->widget()); if (child && child->document()) { QString text; if (i < 9) { text = i18n("&%1 %2", i + 1, child->document()->url().toDisplayString()); } else { text = i18n("%1 %2", i + 1, child->document()->url().toDisplayString()); } QAction *action = menu->addAction(text); action->setIcon(qApp->windowIcon()); action->setCheckable(true); action->setChecked(child == activeKisView()); connect(action, SIGNAL(triggered()), d->windowMapper, SLOT(map())); d->windowMapper->setMapping(action, windows.at(i)); } } updateCaption(); } void KisMainWindow::setActiveSubWindow(QWidget *window) { if (!window) return; QMdiSubWindow *subwin = qobject_cast(window); //dbgKrita << "setActiveSubWindow();" << subwin << d->activeSubWindow; if (subwin && subwin != d->activeSubWindow) { KisView *view = qobject_cast(subwin->widget()); //dbgKrita << "\t" << view << activeView(); if (view && view != activeView()) { d->mdiArea->setActiveSubWindow(subwin); setActiveView(view); } d->activeSubWindow = subwin; } updateWindowMenu(); d->actionManager()->updateGUI(); } void KisMainWindow::configChanged() { KisConfig cfg; QMdiArea::ViewMode viewMode = (QMdiArea::ViewMode)cfg.readEntry("mdi_viewmode", (int)QMdiArea::TabbedView); d->mdiArea->setViewMode(viewMode); Q_FOREACH (QMdiSubWindow *subwin, d->mdiArea->subWindowList()) { subwin->setOption(QMdiSubWindow::RubberBandMove, cfg.readEntry("mdi_rubberband", cfg.useOpenGL())); subwin->setOption(QMdiSubWindow::RubberBandResize, cfg.readEntry("mdi_rubberband", cfg.useOpenGL())); } KConfigGroup group( KSharedConfig::openConfig(), "theme"); d->themeManager->setCurrentTheme(group.readEntry("Theme", "Krita dark")); d->actionManager()->updateGUI(); QBrush brush(cfg.getMDIBackgroundColor()); d->mdiArea->setBackground(brush); QString backgroundImage = cfg.getMDIBackgroundImage(); if (backgroundImage != "") { QImage image(backgroundImage); QBrush brush(image); d->mdiArea->setBackground(brush); } d->mdiArea->update(); } void KisMainWindow::newView(QObject *document) { KisDocument *doc = qobject_cast(document); addViewAndNotifyLoadingCompleted(doc); d->actionManager()->updateGUI(); } void KisMainWindow::newWindow() { KisPart::instance()->createMainWindow()->show(); } void KisMainWindow::closeCurrentWindow() { d->mdiArea->currentSubWindow()->close(); d->actionManager()->updateGUI(); } void KisMainWindow::checkSanity() { // print error if the lcms engine is not available if (!KoColorSpaceEngineRegistry::instance()->contains("icc")) { // need to wait 1 event since exiting here would not work. m_errorMessage = i18n("The Krita LittleCMS color management plugin is not installed. Krita will quit now."); m_dieOnError = true; QTimer::singleShot(0, this, SLOT(showErrorAndDie())); return; } KisPaintOpPresetResourceServer * rserver = KisResourceServerProvider::instance()->paintOpPresetServer(); if (rserver->resources().isEmpty()) { m_errorMessage = i18n("Krita cannot find any brush presets! Krita will quit now."); m_dieOnError = true; QTimer::singleShot(0, this, SLOT(showErrorAndDie())); return; } } void KisMainWindow::showErrorAndDie() { QMessageBox::critical(0, i18nc("@title:window", "Installation error"), m_errorMessage); if (m_dieOnError) { exit(10); } } void KisMainWindow::showAboutApplication() { KisAboutApplication dlg(this); dlg.exec(); } QPointerKisMainWindow::activeKisView() { if (!d->mdiArea) return 0; QMdiSubWindow *activeSubWindow = d->mdiArea->activeSubWindow(); //dbgKrita << "activeKisView" << activeSubWindow; if (!activeSubWindow) return 0; return qobject_cast(activeSubWindow->widget()); } void KisMainWindow::newOptionWidgets(KoCanvasController *controller, const QList > &optionWidgetList) { KIS_ASSERT_RECOVER_NOOP(controller == KoToolManager::instance()->activeCanvasController()); bool isOurOwnView = false; Q_FOREACH (QPointer view, KisPart::instance()->views()) { if (view && view->canvasController() == controller) { isOurOwnView = view->mainWindow() == this; } } if (!isOurOwnView) return; Q_FOREACH (QWidget *w, optionWidgetList) { #ifdef Q_OS_OSX w->setAttribute(Qt::WA_MacSmallSize, true); #endif w->setFont(KoDockRegistry::dockFont()); } if (d->toolOptionsDocker) { d->toolOptionsDocker->setOptionWidgets(optionWidgetList); } else { d->viewManager->paintOpBox()->newOptionWidgets(optionWidgetList); } } void KisMainWindow::applyDefaultSettings(QPrinter &printer) { if (!d->activeView) return; QString title = d->activeView->document()->documentInfo()->aboutInfo("title"); if (title.isEmpty()) { title = d->activeView->document()->url().fileName(); // strip off the native extension (I don't want foobar.kwd.ps when printing into a file) QString extension = KisMimeDatabase::suffixesForMimeType(d->activeView->document()->outputMimeType()).first(); if (title.endsWith(extension)) { title.chop(extension.length()); } } if (title.isEmpty()) { // #139905 title = i18n("%1 unsaved document (%2)", qApp->applicationDisplayName(), QLocale().toString(QDate::currentDate(), QLocale::ShortFormat)); } printer.setDocName(title); } void KisMainWindow::createActions() { KisActionManager *actionManager = d->actionManager(); KisConfig cfg; actionManager->createStandardAction(KStandardAction::New, this, SLOT(slotFileNew())); actionManager->createStandardAction(KStandardAction::Open, this, SLOT(slotFileOpen())); actionManager->createStandardAction(KStandardAction::Quit, this, SLOT(slotFileQuit())); actionManager->createStandardAction(KStandardAction::ConfigureToolbars, this, SLOT(slotConfigureToolbars())); actionManager->createStandardAction(KStandardAction::FullScreen, this, SLOT(viewFullscreen(bool))); d->recentFiles = KStandardAction::openRecent(this, SLOT(slotFileOpenRecent(QUrl)), actionCollection()); connect(d->recentFiles, SIGNAL(recentListCleared()), this, SLOT(saveRecentFiles())); KSharedConfigPtr configPtr = KSharedConfig::openConfig(); d->recentFiles->loadEntries(configPtr->group("RecentFiles")); d->saveAction = actionManager->createStandardAction(KStandardAction::Save, this, SLOT(slotFileSave())); d->saveAction->setActivationFlags(KisAction::ACTIVE_IMAGE); d->saveActionAs = actionManager->createStandardAction(KStandardAction::SaveAs, this, SLOT(slotFileSaveAs())); d->saveActionAs->setActivationFlags(KisAction::ACTIVE_IMAGE); // d->printAction = actionManager->createStandardAction(KStandardAction::Print, this, SLOT(slotFilePrint())); // d->printAction->setActivationFlags(KisAction::ACTIVE_IMAGE); // d->printActionPreview = actionManager->createStandardAction(KStandardAction::PrintPreview, this, SLOT(slotFilePrintPreview())); // d->printActionPreview->setActivationFlags(KisAction::ACTIVE_IMAGE); d->undo = actionManager->createStandardAction(KStandardAction::Undo, this, SLOT(undo())); d->undo ->setActivationFlags(KisAction::ACTIVE_IMAGE); d->redo = actionManager->createStandardAction(KStandardAction::Redo, this, SLOT(redo())); d->redo->setActivationFlags(KisAction::ACTIVE_IMAGE); // d->exportPdf = actionManager->createAction("file_export_pdf"); // connect(d->exportPdf, SIGNAL(triggered()), this, SLOT(exportToPdf())); d->importAnimation = actionManager->createAction("file_import_animation"); connect(d->importAnimation, SIGNAL(triggered()), this, SLOT(importAnimation())); d->closeAll = actionManager->createAction("file_close_all"); connect(d->closeAll, SIGNAL(triggered()), this, SLOT(slotFileCloseAll())); // d->reloadFile = actionManager->createAction("file_reload_file"); // d->reloadFile->setActivationFlags(KisAction::CURRENT_IMAGE_MODIFIED); // connect(d->reloadFile, SIGNAL(triggered(bool)), this, SLOT(slotReloadFile())); d->importFile = actionManager->createAction("file_import_file"); connect(d->importFile, SIGNAL(triggered(bool)), this, SLOT(slotImportFile())); d->exportFile = actionManager->createAction("file_export_file"); connect(d->exportFile, SIGNAL(triggered(bool)), this, SLOT(slotExportFile())); /* The following entry opens the document information dialog. Since the action is named so it intends to show data this entry should not have a trailing ellipses (...). */ d->showDocumentInfo = actionManager->createAction("file_documentinfo"); connect(d->showDocumentInfo, SIGNAL(triggered(bool)), this, SLOT(slotDocumentInfo())); d->themeManager->setThemeMenuAction(new KActionMenu(i18nc("@action:inmenu", "&Themes"), this)); d->themeManager->registerThemeActions(actionCollection()); connect(d->themeManager, SIGNAL(signalThemeChanged()), this, SLOT(slotThemeChanged())); d->toggleDockers = actionManager->createAction("view_toggledockers"); cfg.showDockers(true); d->toggleDockers->setChecked(true); connect(d->toggleDockers, SIGNAL(toggled(bool)), SLOT(toggleDockersVisibility(bool))); d->toggleDockerTitleBars = actionManager->createAction("view_toggledockertitlebars"); d->toggleDockerTitleBars->setChecked(cfg.showDockerTitleBars()); connect(d->toggleDockerTitleBars, SIGNAL(toggled(bool)), SLOT(showDockerTitleBars(bool))); actionCollection()->addAction("settings_dockers_menu", d->dockWidgetMenu); actionCollection()->addAction("window", d->windowMenu); d->mdiCascade = actionManager->createAction("windows_cascade"); connect(d->mdiCascade, SIGNAL(triggered()), d->mdiArea, SLOT(cascadeSubWindows())); d->mdiTile = actionManager->createAction("windows_tile"); connect(d->mdiTile, SIGNAL(triggered()), d->mdiArea, SLOT(tileSubWindows())); d->mdiNextWindow = actionManager->createAction("windows_next"); connect(d->mdiNextWindow, SIGNAL(triggered()), d->mdiArea, SLOT(activateNextSubWindow())); d->mdiPreviousWindow = actionManager->createAction("windows_previous"); connect(d->mdiPreviousWindow, SIGNAL(triggered()), d->mdiArea, SLOT(activatePreviousSubWindow())); d->newWindow = actionManager->createAction("view_newwindow"); connect(d->newWindow, SIGNAL(triggered(bool)), this, SLOT(newWindow())); d->close = actionManager->createAction("file_close"); connect(d->close, SIGNAL(triggered()), SLOT(closeCurrentWindow())); actionManager->createStandardAction(KStandardAction::Preferences, this, SLOT(slotPreferences())); for (int i = 0; i < 2; i++) { d->expandingSpacers[i] = new KisAction(i18n("Expanding Spacer")); d->expandingSpacers[i]->setDefaultWidget(new QWidget(this)); d->expandingSpacers[i]->defaultWidget()->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); actionManager->addAction(QString("expanding_spacer_%1").arg(i), d->expandingSpacers[i]); } } void KisMainWindow::applyToolBarLayout() { const bool isPlastiqueStyle = style()->objectName() == "plastique"; Q_FOREACH (KToolBar *toolBar, toolBars()) { toolBar->layout()->setSpacing(4); if (isPlastiqueStyle) { toolBar->setContentsMargins(0, 0, 0, 2); } //Hide text for buttons with an icon in the toolbar Q_FOREACH (QAction *ac, toolBar->actions()){ if (ac->icon().pixmap(QSize(1,1)).isNull() == false){ ac->setPriority(QAction::LowPriority); }else { ac->setIcon(QIcon()); } } } } void KisMainWindow::initializeGeometry() { // if the user didn's specify the geometry on the command line (does anyone do that still?), // we first figure out some good default size and restore the x,y position. See bug 285804Z. KConfigGroup cfg( KSharedConfig::openConfig(), "MainWindow"); QByteArray geom = QByteArray::fromBase64(cfg.readEntry("ko_geometry", QByteArray())); if (!restoreGeometry(geom)) { const int scnum = QApplication::desktop()->screenNumber(parentWidget()); QRect desk = QApplication::desktop()->availableGeometry(scnum); // if the desktop is virtual then use virtual screen size if (QApplication::desktop()->isVirtualDesktop()) { desk = QApplication::desktop()->availableGeometry(QApplication::desktop()->screen(scnum)); } quint32 x = desk.x(); quint32 y = desk.y(); quint32 w = 0; quint32 h = 0; // Default size -- maximize on small screens, something useful on big screens const int deskWidth = desk.width(); if (deskWidth > 1024) { // a nice width, and slightly less than total available // height to componensate for the window decs w = (deskWidth / 3) * 2; h = (desk.height() / 3) * 2; } else { w = desk.width(); h = desk.height(); } x += (desk.width() - w) / 2; y += (desk.height() - h) / 2; move(x,y); setGeometry(geometry().x(), geometry().y(), w, h); } restoreWorkspace(QByteArray::fromBase64(cfg.readEntry("ko_windowstate", QByteArray()))); } void KisMainWindow::showManual() { QDesktopServices::openUrl(QUrl("https://docs.krita.org")); } void KisMainWindow::showDockerTitleBars(bool show) { Q_FOREACH (QDockWidget *dock, dockWidgets()) { if (dock->titleBarWidget()) { const bool isCollapsed = (dock->widget() && dock->widget()->isHidden()) || !dock->widget(); dock->titleBarWidget()->setVisible(show || (dock->isFloating() && isCollapsed)); } } KisConfig cfg; cfg.setShowDockerTitleBars(show); } void KisMainWindow::moveEvent(QMoveEvent *e) { if (qApp->desktop()->screenNumber(this) != qApp->desktop()->screenNumber(e->oldPos())) { KisConfigNotifier::instance()->notifyConfigChanged(); } } #include diff --git a/libs/ui/forms/wdggeneralsettings.ui b/libs/ui/forms/wdggeneralsettings.ui index 7edacee5df..3b87424cd5 100644 --- a/libs/ui/forms/wdggeneralsettings.ui +++ b/libs/ui/forms/wdggeneralsettings.ui @@ -1,684 +1,684 @@ WdgGeneralSettings 0 0 759 468 0 0 552 295 Qt::LeftToRight - 0 + 2 Cursor 10 10 10 10 10 10 0 0 Cursor Shape: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 0 Outline Shape: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 0 200 0 Show brush outline while painting Qt::Vertical 20 40 Window 0 0 Multiple Document Mode: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 0 1 Subwindows Tabs Background Image (overrides color): 200 0 QFrame::StyledPanel QFrame::Sunken ... 0 0 Clear Window Background: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 0 Don't show contents when moving sub-windows: 0 0 Show on-canvas popup messages: Qt::Vertical 20 40 Enable Hi-DPI support: Allow only one instance of Krita: Tools 10 10 10 10 10 Tool Options Location (needs restart) In Doc&ker In Tool&bar true Switch Control/Alt Selection Modifiers Enable Touch Painting Qt::Vertical 20 40 Qt::Horizontal 40 20 Miscellaneous 0 0 Qt::RightToLeft Autosave every: true 0 0 75 0 min 1 1440 5 15 Compress .kra files more (slows loading/saving) Create backup file On importing images as layers, convert to the image colorspace 0 0 Undo stack size: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 0 75 0 0 1000 5 30 0 0 Number of Palette Presets Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 0 75 0 10 30 Show root layer Hide splash screen on startup Warning: if you enable this setting and the file dialogs do weird stuff, do not report a bug. Enable native file dialogs (warning: may not work correctly on some systems) Maximum brush size: 0 0 The maximum diameter of a brush in pixels. px 100 10000 1000 (Needs restart) Qt::Vertical 504 13 Recalculate animation cache in background KisIntParseSpinBox QSpinBox
kis_int_parse_spin_box.h
KisColorButton QPushButton
kis_color_button.h
diff --git a/libs/ui/kis_paintop_box.cc b/libs/ui/kis_paintop_box.cc index 7981cec326..d97b0f4353 100644 --- a/libs/ui/kis_paintop_box.cc +++ b/libs/ui/kis_paintop_box.cc @@ -1,1324 +1,1346 @@ /* * kis_paintop_box.cc - part of KImageShop/Krayon/Krita * * Copyright (c) 2004 Boudewijn Rempt (boud@valdyas.org) * Copyright (c) 2009-2011 Sven Langkamp (sven.langkamp@gmail.com) * Copyright (c) 2010 Lukáš Tvrdý * Copyright (C) 2011 Silvio Heinrich * Copyright (C) 2011 Srikanth Tiyyagura * Copyright (c) 2014 Mohit Goyal * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_paintop_box.h" #include #include #include #include #include #include #include +#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_canvas2.h" #include "kis_node_manager.h" #include "KisViewManager.h" #include "kis_canvas_resource_provider.h" #include "kis_resource_server_provider.h" #include "kis_favorite_resource_manager.h" #include "kis_config.h" #include "widgets/kis_popup_button.h" #include "widgets/kis_tool_options_popup.h" #include "widgets/kis_paintop_presets_popup.h" #include "widgets/kis_tool_options_popup.h" #include "widgets/kis_paintop_presets_chooser_popup.h" #include "widgets/kis_workspace_chooser.h" #include "widgets/kis_paintop_list_widget.h" #include "widgets/kis_slider_spin_box.h" #include "widgets/kis_cmb_composite.h" #include "widgets/kis_widget_chooser.h" #include "tool/kis_tool.h" #include "kis_signals_blocker.h" #include "kis_action_manager.h" #include "kis_highlighted_button.h" typedef KoResourceServerSimpleConstruction > KisPaintOpPresetResourceServer; typedef KoResourceServerAdapter > KisPaintOpPresetResourceServerAdapter; KisPaintopBox::KisPaintopBox(KisViewManager *view, QWidget *parent, const char *name) : QWidget(parent) , m_resourceProvider(view->resourceProvider()) , m_optionWidget(0) , m_toolOptionsPopupButton(0) , m_brushEditorPopupButton(0) , m_presetSelectorPopupButton(0) , m_toolOptionsPopup(0) , m_viewManager(view) , m_previousNode(0) , m_currTabletToolID(KoInputDevice::invalid()) , m_presetsEnabled(true) , m_blockUpdate(false) , m_dirtyPresetsEnabled(false) , m_eraserBrushSizeEnabled(false) , m_eraserBrushOpacityEnabled(false) { Q_ASSERT(view != 0); setObjectName(name); KisConfig cfg; m_dirtyPresetsEnabled = cfg.useDirtyPresets(); m_eraserBrushSizeEnabled = cfg.useEraserBrushSize(); m_eraserBrushOpacityEnabled = cfg.useEraserBrushOpacity(); KAcceleratorManager::setNoAccel(this); setWindowTitle(i18n("Painter's Toolchest")); KConfigGroup grp = KSharedConfig::openConfig()->group("krita").group("Toolbar BrushesAndStuff"); int iconsize = grp.readEntry("IconSize", 32); if (!cfg.toolOptionsInDocker()) { m_toolOptionsPopupButton = new KisPopupButton(this); m_toolOptionsPopupButton->setIcon(KisIconUtils::loadIcon("configure")); m_toolOptionsPopupButton->setToolTip(i18n("Tool Settings")); m_toolOptionsPopupButton->setFixedSize(iconsize, iconsize); } m_brushEditorPopupButton = new KisPopupButton(this); m_brushEditorPopupButton->setIcon(KisIconUtils::loadIcon("paintop_settings_02")); m_brushEditorPopupButton->setToolTip(i18n("Edit brush settings")); m_brushEditorPopupButton->setFixedSize(iconsize, iconsize); m_presetSelectorPopupButton = new KisPopupButton(this); m_presetSelectorPopupButton->setIcon(KisIconUtils::loadIcon("paintop_settings_01")); m_presetSelectorPopupButton->setToolTip(i18n("Choose brush preset")); m_presetSelectorPopupButton->setFixedSize(iconsize, iconsize); m_eraseModeButton = new KisHighlightedToolButton(this); m_eraseModeButton->setFixedSize(iconsize, iconsize); m_eraseModeButton->setCheckable(true); m_eraseAction = m_viewManager->actionManager()->createAction("erase_action"); m_eraseModeButton->setDefaultAction(m_eraseAction); m_reloadButton = new QToolButton(this); m_reloadButton->setFixedSize(iconsize, iconsize); m_reloadAction = m_viewManager->actionManager()->createAction("reload_preset_action"); m_reloadButton->setDefaultAction(m_reloadAction); m_alphaLockButton = new KisHighlightedToolButton(this); m_alphaLockButton->setFixedSize(iconsize, iconsize); m_alphaLockButton->setCheckable(true); KisAction* alphaLockAction = m_viewManager->actionManager()->createAction("preserve_alpha"); m_alphaLockButton->setDefaultAction(alphaLockAction); // pen pressure m_disablePressureButton = new KisHighlightedToolButton(this); m_disablePressureButton->setFixedSize(iconsize, iconsize); m_disablePressureButton->setCheckable(true); m_disablePressureAction = m_viewManager->actionManager()->createAction("disable_pressure"); m_disablePressureButton->setDefaultAction(m_disablePressureAction); // horizontal and vertical mirror toolbar buttons // mirror tool options for the X Mirror QMenu *toolbarMenuXMirror = new QMenu(); KisAction* hideCanvasDecorationsX = m_viewManager->actionManager()->createAction("mirrorX-hideDecorations"); hideCanvasDecorationsX->setCheckable(true); hideCanvasDecorationsX->setText(i18n("Hide Mirror Line")); toolbarMenuXMirror->addAction(hideCanvasDecorationsX); KisAction* lockActionX = m_viewManager->actionManager()->createAction("mirrorX-lock"); lockActionX->setText(i18n("Lock")); lockActionX->setCheckable(true); toolbarMenuXMirror->addAction(lockActionX); KisAction* moveToCenterActionX = m_viewManager->actionManager()->createAction("mirrorX-moveToCenter"); moveToCenterActionX->setCheckable(false); moveToCenterActionX->setText(i18n("Move to Canvas Center")); toolbarMenuXMirror->addAction(moveToCenterActionX); // mirror tool options for the Y Mirror QMenu *toolbarMenuYMirror = new QMenu(); KisAction* hideCanvasDecorationsY = m_viewManager->actionManager()->createAction("mirrorY-hideDecorations"); hideCanvasDecorationsY->setCheckable(true); hideCanvasDecorationsY->setText(i18n("Hide Mirror Line")); toolbarMenuYMirror->addAction(hideCanvasDecorationsY); KisAction* lockActionY = m_viewManager->actionManager()->createAction("mirrorY-lock"); lockActionY->setText(i18n("Lock")); lockActionY->setCheckable(true); toolbarMenuYMirror->addAction(lockActionY); KisAction* moveToCenterActionY = m_viewManager->actionManager()->createAction("mirrorY-moveToCenter"); moveToCenterActionY->setCheckable(false); moveToCenterActionY->setText(i18n("Move to Canvas Center")); toolbarMenuYMirror->addAction(moveToCenterActionY); // create horizontal and vertical mirror buttons m_hMirrorButton = new KisHighlightedToolButton(this); int menuPadding = 10; m_hMirrorButton->setFixedSize(iconsize + menuPadding, iconsize); m_hMirrorButton->setCheckable(true); m_hMirrorAction = m_viewManager->actionManager()->createAction("hmirror_action"); m_hMirrorButton->setDefaultAction(m_hMirrorAction); m_hMirrorButton->setMenu(toolbarMenuXMirror); m_hMirrorButton->setPopupMode(QToolButton::MenuButtonPopup); m_vMirrorButton = new KisHighlightedToolButton(this); m_vMirrorButton->setFixedSize(iconsize + menuPadding, iconsize); m_vMirrorButton->setCheckable(true); m_vMirrorAction = m_viewManager->actionManager()->createAction("vmirror_action"); m_vMirrorButton->setDefaultAction(m_vMirrorAction); m_vMirrorButton->setMenu(toolbarMenuYMirror); m_vMirrorButton->setPopupMode(QToolButton::MenuButtonPopup); // add connections for horizontal and mirrror buttons connect(lockActionX, SIGNAL(toggled(bool)), this, SLOT(slotLockXMirrorToggle(bool))); connect(lockActionY, SIGNAL(toggled(bool)), this, SLOT(slotLockYMirrorToggle(bool))); connect(moveToCenterActionX, SIGNAL(triggered(bool)), this, SLOT(slotMoveToCenterMirrorX())); connect(moveToCenterActionY, SIGNAL(triggered(bool)), this, SLOT(slotMoveToCenterMirrorY())); connect(hideCanvasDecorationsX, SIGNAL(toggled(bool)), this, SLOT(slotHideDecorationMirrorX(bool))); connect(hideCanvasDecorationsY, SIGNAL(toggled(bool)), this, SLOT(slotHideDecorationMirrorY(bool))); const bool sliderLabels = cfg.sliderLabels(); int sliderWidth; if (sliderLabels) { sliderWidth = 150 * logicalDpiX() / 96; } else { sliderWidth = 120 * logicalDpiX() / 96; } for (int i = 0; i < 3; ++i) { m_sliderChooser[i] = new KisWidgetChooser(i + 1); KisDoubleSliderSpinBox* slOpacity; KisDoubleSliderSpinBox* slFlow; KisDoubleSliderSpinBox* slSize; if (sliderLabels) { slOpacity = m_sliderChooser[i]->addWidget("opacity"); slFlow = m_sliderChooser[i]->addWidget("flow"); slSize = m_sliderChooser[i]->addWidget("size"); slOpacity->setPrefix(QString("%1 ").arg(i18n("Opacity:"))); slFlow->setPrefix(QString("%1 ").arg(i18n("Flow:"))); slSize->setPrefix(QString("%1 ").arg(i18n("Size:"))); } else { slOpacity = m_sliderChooser[i]->addWidget("opacity", i18n("Opacity:")); slFlow = m_sliderChooser[i]->addWidget("flow", i18n("Flow:")); slSize = m_sliderChooser[i]->addWidget("size", i18n("Size:")); } slOpacity->setRange(0.0, 1.0, 2); slOpacity->setValue(1.0); slOpacity->setSingleStep(0.05); slOpacity->setMinimumWidth(qMax(sliderWidth, slOpacity->sizeHint().width())); slOpacity->setFixedHeight(iconsize); slOpacity->setBlockUpdateSignalOnDrag(true); slFlow->setRange(0.0, 1.0, 2); slFlow->setValue(1.0); slFlow->setSingleStep(0.05); slFlow->setMinimumWidth(qMax(sliderWidth, slFlow->sizeHint().width())); slFlow->setFixedHeight(iconsize); slFlow->setBlockUpdateSignalOnDrag(true); slSize->setRange(0, cfg.readEntry("maximumBrushSize", 1000), 2); slSize->setValue(100); slSize->setSingleStep(1); slSize->setExponentRatio(3.0); slSize->setSuffix(i18n(" px")); slSize->setMinimumWidth(qMax(sliderWidth, slSize->sizeHint().width())); slSize->setFixedHeight(iconsize); slSize->setBlockUpdateSignalOnDrag(true); m_sliderChooser[i]->chooseWidget(cfg.toolbarSlider(i + 1)); } m_cmbCompositeOp = new KisCompositeOpComboBox(); m_cmbCompositeOp->setFixedHeight(iconsize); Q_FOREACH (KisAction * a, m_cmbCompositeOp->blendmodeActions()) { m_viewManager->actionManager()->addAction(a->text(), a); } m_workspaceWidget = new KisPopupButton(this); m_workspaceWidget->setIcon(KisIconUtils::loadIcon("view-choose")); m_workspaceWidget->setToolTip(i18n("Choose workspace")); m_workspaceWidget->setFixedSize(iconsize, iconsize); m_workspaceWidget->setPopupWidget(new KisWorkspaceChooser(view)); QHBoxLayout* baseLayout = new QHBoxLayout(this); m_paintopWidget = new QWidget(this); baseLayout->addWidget(m_paintopWidget); baseLayout->setSpacing(4); baseLayout->setContentsMargins(0, 0, 0, 0); m_layout = new QHBoxLayout(m_paintopWidget); if (!cfg.toolOptionsInDocker()) { m_layout->addWidget(m_toolOptionsPopupButton); } m_layout->addWidget(m_brushEditorPopupButton); m_layout->addWidget(m_presetSelectorPopupButton); m_layout->setSpacing(4); m_layout->setContentsMargins(0, 0, 0, 0); QWidget* compositeActions = new QWidget(this); QHBoxLayout* compositeLayout = new QHBoxLayout(compositeActions); compositeLayout->addWidget(m_cmbCompositeOp); compositeLayout->addWidget(m_eraseModeButton); compositeLayout->addWidget(m_alphaLockButton); compositeLayout->setSpacing(4); compositeLayout->setContentsMargins(0, 0, 0, 0); compositeLayout->addWidget(m_reloadButton); QWidgetAction * action; action = new QWidgetAction(this); view->actionCollection()->addAction("composite_actions", action); action->setText(i18n("Brush composite")); action->setDefaultWidget(compositeActions); QWidget* compositePressure = new QWidget(this); QHBoxLayout* pressureLayout = new QHBoxLayout(compositePressure); pressureLayout->addWidget(m_disablePressureButton); pressureLayout->setSpacing(4); pressureLayout->setContentsMargins(0, 0, 0, 0); action = new QWidgetAction(this); view->actionCollection()->addAction("pressure_action", action); action->setText(i18n("Pressure usage (small button)")); action->setDefaultWidget(compositePressure); action = new QWidgetAction(this); KisActionRegistry::instance()->propertizeAction("brushslider1", action); view->actionCollection()->addAction("brushslider1", action); action->setDefaultWidget(m_sliderChooser[0]); connect(action, SIGNAL(triggered()), m_sliderChooser[0], SLOT(showPopupWidget())); connect(m_viewManager->mainWindow(), SIGNAL(themeChanged()), m_sliderChooser[0], SLOT(updateThemedIcons())); action = new QWidgetAction(this); KisActionRegistry::instance()->propertizeAction("brushslider2", action); view->actionCollection()->addAction("brushslider2", action); action->setDefaultWidget(m_sliderChooser[1]); connect(action, SIGNAL(triggered()), m_sliderChooser[1], SLOT(showPopupWidget())); connect(m_viewManager->mainWindow(), SIGNAL(themeChanged()), m_sliderChooser[1], SLOT(updateThemedIcons())); action = new QWidgetAction(this); KisActionRegistry::instance()->propertizeAction("brushslider3", action); view->actionCollection()->addAction("brushslider3", action); action->setDefaultWidget(m_sliderChooser[2]); connect(action, SIGNAL(triggered()), m_sliderChooser[2], SLOT(showPopupWidget())); connect(m_viewManager->mainWindow(), SIGNAL(themeChanged()), m_sliderChooser[2], SLOT(updateThemedIcons())); action = new QWidgetAction(this); KisActionRegistry::instance()->propertizeAction("next_favorite_preset", action); view->actionCollection()->addAction("next_favorite_preset", action); connect(action, SIGNAL(triggered()), this, SLOT(slotNextFavoritePreset())); action = new QWidgetAction(this); KisActionRegistry::instance()->propertizeAction("previous_favorite_preset", action); view->actionCollection()->addAction("previous_favorite_preset", action); connect(action, SIGNAL(triggered()), this, SLOT(slotPreviousFavoritePreset())); action = new QWidgetAction(this); KisActionRegistry::instance()->propertizeAction("previous_preset", action); view->actionCollection()->addAction("previous_preset", action); connect(action, SIGNAL(triggered()), this, SLOT(slotSwitchToPreviousPreset())); if (!cfg.toolOptionsInDocker()) { action = new QWidgetAction(this); KisActionRegistry::instance()->propertizeAction("show_tool_options", action); view->actionCollection()->addAction("show_tool_options", action); connect(action, SIGNAL(triggered()), m_toolOptionsPopupButton, SLOT(showPopupWidget())); } action = new QWidgetAction(this); KisActionRegistry::instance()->propertizeAction("show_brush_editor", action); view->actionCollection()->addAction("show_brush_editor", action); connect(action, SIGNAL(triggered()), m_brushEditorPopupButton, SLOT(showPopupWidget())); action = new QWidgetAction(this); KisActionRegistry::instance()->propertizeAction("show_brush_presets", action); view->actionCollection()->addAction("show_brush_presets", action); connect(action, SIGNAL(triggered()), m_presetSelectorPopupButton, SLOT(showPopupWidget())); QWidget* mirrorActions = new QWidget(this); QHBoxLayout* mirrorLayout = new QHBoxLayout(mirrorActions); mirrorLayout->addWidget(m_hMirrorButton); mirrorLayout->addWidget(m_vMirrorButton); mirrorLayout->setSpacing(4); mirrorLayout->setContentsMargins(0, 0, 0, 0); action = new QWidgetAction(this); KisActionRegistry::instance()->propertizeAction("mirror_actions", action); action->setDefaultWidget(mirrorActions); view->actionCollection()->addAction("mirror_actions", action); action = new QWidgetAction(this); KisActionRegistry::instance()->propertizeAction("workspaces", action); view->actionCollection()->addAction("workspaces", action); action->setDefaultWidget(m_workspaceWidget); if (!cfg.toolOptionsInDocker()) { m_toolOptionsPopup = new KisToolOptionsPopup(); m_toolOptionsPopupButton->setPopupWidget(m_toolOptionsPopup); m_toolOptionsPopup->switchDetached(false); } m_presetsPopup = new KisPaintOpPresetsPopup(m_resourceProvider); m_brushEditorPopupButton->setPopupWidget(m_presetsPopup); m_presetsPopup->parentWidget()->setWindowTitle(i18n("Brush Editor")); connect(m_presetsPopup, SIGNAL(brushEditorShown()), SLOT(slotUpdateOptionsWidgetPopup())); connect(m_viewManager->mainWindow(), SIGNAL(themeChanged()), m_presetsPopup, SLOT(updateThemedIcons())); m_presetsChooserPopup = new KisPaintOpPresetsChooserPopup(); m_presetSelectorPopupButton->setPopupWidget(m_presetsChooserPopup); m_currCompositeOpID = KoCompositeOpRegistry::instance().getDefaultCompositeOp().id(); slotNodeChanged(view->activeNode()); // Get all the paintops QList keys = KisPaintOpRegistry::instance()->keys(); QList factoryList; Q_FOREACH (const QString & paintopId, keys) { factoryList.append(KisPaintOpRegistry::instance()->get(paintopId)); } m_presetsPopup->setPaintOpList(factoryList); connect(m_presetsPopup , SIGNAL(paintopActivated(QString)) , SLOT(slotSetPaintop(QString))); connect(m_presetsPopup , SIGNAL(savePresetClicked()) , SLOT(slotSaveActivePreset())); connect(m_presetsPopup , SIGNAL(defaultPresetClicked()) , SLOT(slotSetupDefaultPreset())); connect(m_presetsPopup , SIGNAL(signalResourceSelected(KoResource*)), SLOT(resourceSelected(KoResource*))); connect(m_presetsPopup , SIGNAL(reloadPresetClicked()) , SLOT(slotReloadPreset())); connect(m_presetsPopup , SIGNAL(dirtyPresetToggled(bool)) , SLOT(slotDirtyPresetToggled(bool))); connect(m_presetsPopup , SIGNAL(eraserBrushSizeToggled(bool)) , SLOT(slotEraserBrushSizeToggled(bool))); connect(m_presetsPopup , SIGNAL(eraserBrushOpacityToggled(bool)) , SLOT(slotEraserBrushOpacityToggled(bool))); connect(m_presetsChooserPopup, SIGNAL(resourceSelected(KoResource*)) , SLOT(resourceSelected(KoResource*))); connect(m_presetsChooserPopup, SIGNAL(resourceClicked(KoResource*)) , SLOT(resourceSelected(KoResource*))); connect(m_resourceProvider , SIGNAL(sigNodeChanged(const KisNodeSP)) , SLOT(slotNodeChanged(const KisNodeSP))); connect(m_cmbCompositeOp , SIGNAL(currentIndexChanged(int)) , SLOT(slotSetCompositeMode(int))); connect(m_eraseAction , SIGNAL(toggled(bool)) , SLOT(slotToggleEraseMode(bool))); connect(alphaLockAction , SIGNAL(toggled(bool)) , SLOT(slotToggleAlphaLockMode(bool))); connect(m_disablePressureAction , SIGNAL(toggled(bool)) , SLOT(slotDisablePressureMode(bool))); m_disablePressureAction->setChecked(true); connect(m_hMirrorAction , SIGNAL(toggled(bool)) , SLOT(slotHorizontalMirrorChanged(bool))); connect(m_vMirrorAction , SIGNAL(toggled(bool)) , SLOT(slotVerticalMirrorChanged(bool))); connect(m_reloadAction , SIGNAL(triggered()) , SLOT(slotReloadPreset())); connect(m_sliderChooser[0]->getWidget("opacity"), SIGNAL(valueChanged(qreal)), SLOT(slotSlider1Changed())); connect(m_sliderChooser[0]->getWidget("flow") , SIGNAL(valueChanged(qreal)), SLOT(slotSlider1Changed())); connect(m_sliderChooser[0]->getWidget("size") , SIGNAL(valueChanged(qreal)), SLOT(slotSlider1Changed())); connect(m_sliderChooser[1]->getWidget("opacity"), SIGNAL(valueChanged(qreal)), SLOT(slotSlider2Changed())); connect(m_sliderChooser[1]->getWidget("flow") , SIGNAL(valueChanged(qreal)), SLOT(slotSlider2Changed())); connect(m_sliderChooser[1]->getWidget("size") , SIGNAL(valueChanged(qreal)), SLOT(slotSlider2Changed())); connect(m_sliderChooser[2]->getWidget("opacity"), SIGNAL(valueChanged(qreal)), SLOT(slotSlider3Changed())); connect(m_sliderChooser[2]->getWidget("flow") , SIGNAL(valueChanged(qreal)), SLOT(slotSlider3Changed())); connect(m_sliderChooser[2]->getWidget("size") , SIGNAL(valueChanged(qreal)), SLOT(slotSlider3Changed())); //Needed to connect canvas to favorite resource manager connect(m_viewManager->resourceProvider(), SIGNAL(sigFGColorChanged(KoColor)), SLOT(slotUnsetEraseMode())); m_favoriteResourceManager = new KisFavoriteResourceManager(this); connect(m_resourceProvider, SIGNAL(sigFGColorUsed(KoColor)), m_favoriteResourceManager, SLOT(slotAddRecentColor(KoColor))); connect(m_resourceProvider, SIGNAL(sigFGColorChanged(KoColor)), m_favoriteResourceManager, SLOT(slotChangeFGColorSelector(KoColor))); connect(m_resourceProvider, SIGNAL(sigBGColorChanged(KoColor)), m_favoriteResourceManager, SLOT(slotSetBGColor(KoColor))); // cold initialization m_favoriteResourceManager->slotChangeFGColorSelector(m_resourceProvider->fgColor()); m_favoriteResourceManager->slotSetBGColor(m_resourceProvider->bgColor()); connect(m_favoriteResourceManager, SIGNAL(sigSetFGColor(KoColor)), m_resourceProvider, SLOT(slotSetFGColor(KoColor))); connect(m_favoriteResourceManager, SIGNAL(sigSetBGColor(KoColor)), m_resourceProvider, SLOT(slotSetBGColor(KoColor))); connect(m_favoriteResourceManager, SIGNAL(sigEnableChangeColor(bool)), m_resourceProvider, SLOT(slotResetEnableFGChange(bool))); connect(view->mainWindow(), SIGNAL(themeChanged()), this, SLOT(slotUpdateSelectionIcon())); slotInputDeviceChanged(KoToolManager::instance()->currentInputDevice()); } KisPaintopBox::~KisPaintopBox() { KisConfig cfg; QMapIterator iter(m_tabletToolMap); while (iter.hasNext()) { iter.next(); //qDebug() << "Writing last used preset for" << iter.key().pointer << iter.key().uniqueID << iter.value().preset->name(); if ((iter.key().pointer) == QTabletEvent::Eraser) { cfg.writeEntry(QString("LastEraser_%1").arg(iter.key().uniqueID) , iter.value().preset->name()); } else { cfg.writeEntry(QString("LastPreset_%1").arg(iter.key().uniqueID) , iter.value().preset->name()); } } // Do not delete the widget, since it it is global to the application, not owned by the view m_presetsPopup->setPaintOpSettingsWidget(0); qDeleteAll(m_paintopOptionWidgets); delete m_favoriteResourceManager; for (int i = 0; i < 3; ++i) { delete m_sliderChooser[i]; } } void KisPaintopBox::restoreResource(KoResource* resource) { KisPaintOpPreset* preset = dynamic_cast(resource); //qDebug() << "restoreResource" << resource << preset; if (preset) { setCurrentPaintop(preset); m_presetsPopup->setPresetImage(preset->image()); m_presetsPopup->resourceSelected(resource); } } void KisPaintopBox::newOptionWidgets(const QList > &optionWidgetList) { if (m_toolOptionsPopup) { m_toolOptionsPopup->newOptionWidgets(optionWidgetList); } } void KisPaintopBox::resourceSelected(KoResource* resource) { KisPaintOpPreset* preset = dynamic_cast(resource); if (preset && preset != m_resourceProvider->currentPreset()) { if (!preset->settings()->isLoadable()) return; if (!m_dirtyPresetsEnabled) { KisSignalsBlocker blocker(m_optionWidget); if (!preset->load()) { warnKrita << "failed to load the preset."; } } //qDebug() << "resourceSelected" << resource->name(); setCurrentPaintop(preset); m_presetsPopup->setPresetImage(preset->image()); m_presetsPopup->resourceSelected(resource); } } void KisPaintopBox::setCurrentPaintop(const KoID& paintop) { KisPaintOpPresetSP preset = activePreset(paintop); Q_ASSERT(preset && preset->settings()); //qDebug() << "setCurrentPaintop();" << paintop << preset; setCurrentPaintop(preset); } void KisPaintopBox::setCurrentPaintop(KisPaintOpPresetSP preset) { //qDebug() << "setCurrentPaintop(); " << preset->name(); if (preset == m_resourceProvider->currentPreset()) { if (preset == m_tabletToolMap[m_currTabletToolID].preset) { return; } } Q_ASSERT(preset); const KoID& paintop = preset->paintOp(); m_presetConnections.clear(); if (m_resourceProvider->currentPreset()) { m_resourceProvider->setPreviousPaintOpPreset(m_resourceProvider->currentPreset()); if (m_optionWidget) { m_optionWidget->hide(); } } if (!m_paintopOptionWidgets.contains(paintop)) m_paintopOptionWidgets[paintop] = KisPaintOpRegistry::instance()->get(paintop.id())->createConfigWidget(this); m_optionWidget = m_paintopOptionWidgets[paintop]; KisSignalsBlocker b(m_optionWidget); preset->setOptionsWidget(m_optionWidget); m_optionWidget->setImage(m_viewManager->image()); m_optionWidget->setNode(m_viewManager->activeNode()); m_presetsPopup->setPaintOpSettingsWidget(m_optionWidget); m_resourceProvider->setPaintOpPreset(preset); Q_ASSERT(m_optionWidget && m_presetSelectorPopupButton); m_presetConnections.addConnection(m_optionWidget, SIGNAL(sigConfigurationUpdated()), this, SLOT(slotGuiChangedCurrentPreset())); m_presetConnections.addConnection(m_optionWidget, SIGNAL(sigSaveLockedConfig(KisPropertiesConfigurationSP)), this, SLOT(slotSaveLockedOptionToPreset(KisPropertiesConfigurationSP))); m_presetConnections.addConnection(m_optionWidget, SIGNAL(sigDropLockedConfig(KisPropertiesConfigurationSP)), this, SLOT(slotDropLockedOption(KisPropertiesConfigurationSP))); // load the current brush engine icon for the brush editor toolbar button KisPaintOpFactory* paintOp = KisPaintOpRegistry::instance()->get(paintop.id()); QString pixFilename = KoResourcePaths::findResource("kis_images", paintOp->pixmap()); m_brushEditorPopupButton->setIcon(QIcon(pixFilename)); m_presetsPopup->setCurrentPaintOpId(paintop.id()); ////qDebug() << "\tsetting the new preset for" << m_currTabletToolID.uniqueID << "to" << preset->name(); m_paintOpPresetMap[m_resourceProvider->currentPreset()->paintOp()] = preset; m_tabletToolMap[m_currTabletToolID].preset = preset; m_tabletToolMap[m_currTabletToolID].paintOpID = preset->paintOp(); if (m_presetsPopup->currentPaintOpId() != paintop.id()) { // Must change the paintop as the current one is not supported // by the new colorspace. dbgKrita << "current paintop " << paintop.name() << " was not set, not supported by colorspace"; } } void KisPaintopBox::slotUpdateOptionsWidgetPopup() { KisPaintOpPresetSP preset = m_resourceProvider->currentPreset(); KIS_SAFE_ASSERT_RECOVER_RETURN(preset); KIS_SAFE_ASSERT_RECOVER_RETURN(m_optionWidget); m_optionWidget->setConfigurationSafe(preset->settings()); m_presetsPopup->resourceSelected(preset.data()); m_presetsPopup->updateViewSettings(); // the m_viewManager->image() is set earlier, but the reference will be missing when the stamp button is pressed // need to later do some research on how and when we should be using weak shared pointers (WSP) that creates this situation m_optionWidget->setImage(m_viewManager->image()); } KisPaintOpPresetSP KisPaintopBox::defaultPreset(const KoID& paintOp) { QString defaultName = paintOp.id() + ".kpp"; QString path = KoResourcePaths::findResource("kis_defaultpresets", defaultName); KisPaintOpPresetSP preset = new KisPaintOpPreset(path); if (!preset->load()) { preset = KisPaintOpRegistry::instance()->defaultPreset(paintOp); } Q_ASSERT(preset); Q_ASSERT(preset->valid()); return preset; } KisPaintOpPresetSP KisPaintopBox::activePreset(const KoID& paintOp) { if (m_paintOpPresetMap[paintOp] == 0) { m_paintOpPresetMap[paintOp] = defaultPreset(paintOp); } return m_paintOpPresetMap[paintOp]; } void KisPaintopBox::updateCompositeOp(QString compositeOpID) { if (!m_optionWidget) return; KisSignalsBlocker blocker(m_optionWidget); KisNodeSP node = m_resourceProvider->currentNode(); if (node && node->paintDevice()) { if (!node->paintDevice()->colorSpace()->hasCompositeOp(compositeOpID)) compositeOpID = KoCompositeOpRegistry::instance().getDefaultCompositeOp().id(); { KisSignalsBlocker b1(m_cmbCompositeOp); m_cmbCompositeOp->selectCompositeOp(KoID(compositeOpID)); } if (compositeOpID != m_currCompositeOpID) { m_currCompositeOpID = compositeOpID; } if (compositeOpID == COMPOSITE_ERASE) { m_eraseModeButton->setChecked(true); } else { m_eraseModeButton->setChecked(false); } } } void KisPaintopBox::setWidgetState(int flags) { if (flags & (ENABLE_COMPOSITEOP | DISABLE_COMPOSITEOP)) { m_cmbCompositeOp->setEnabled(flags & ENABLE_COMPOSITEOP); m_eraseModeButton->setEnabled(flags & ENABLE_COMPOSITEOP); } if (flags & (ENABLE_PRESETS | DISABLE_PRESETS)) { m_presetSelectorPopupButton->setEnabled(flags & ENABLE_PRESETS); m_brushEditorPopupButton->setEnabled(flags & ENABLE_PRESETS); } for (int i = 0; i < 3; ++i) { if (flags & (ENABLE_OPACITY | DISABLE_OPACITY)) m_sliderChooser[i]->getWidget("opacity")->setEnabled(flags & ENABLE_OPACITY); if (flags & (ENABLE_FLOW | DISABLE_FLOW)) m_sliderChooser[i]->getWidget("flow")->setEnabled(flags & ENABLE_FLOW); if (flags & (ENABLE_SIZE | DISABLE_SIZE)) m_sliderChooser[i]->getWidget("size")->setEnabled(flags & ENABLE_SIZE); } } void KisPaintopBox::setSliderValue(const QString& sliderID, qreal value) { for (int i = 0; i < 3; ++i) { KisDoubleSliderSpinBox* slider = m_sliderChooser[i]->getWidget(sliderID); KisSignalsBlocker b(slider); slider->setValue(value); } } void KisPaintopBox::slotSetPaintop(const QString& paintOpId) { if (KisPaintOpRegistry::instance()->get(paintOpId) != 0) { KoID id(paintOpId, KisPaintOpRegistry::instance()->get(paintOpId)->name()); //qDebug() << "slotsetpaintop" << id; setCurrentPaintop(id); } } void KisPaintopBox::slotInputDeviceChanged(const KoInputDevice& inputDevice) { TabletToolMap::iterator toolData = m_tabletToolMap.find(inputDevice); //qDebug() << "slotInputDeviceChanged()" << inputDevice.device() << inputDevice.uniqueTabletId(); m_currTabletToolID = TabletToolID(inputDevice); if (toolData == m_tabletToolMap.end()) { KisConfig cfg; KisPaintOpPresetResourceServer *rserver = KisResourceServerProvider::instance()->paintOpPresetServer(false); KisPaintOpPresetSP preset; if (inputDevice.pointer() == QTabletEvent::Eraser) { preset = rserver->resourceByName(cfg.readEntry(QString("LastEraser_%1").arg(inputDevice.uniqueTabletId()), "Eraser_circle")); } else { preset = rserver->resourceByName(cfg.readEntry(QString("LastPreset_%1").arg(inputDevice.uniqueTabletId()), "Basic_tip_default")); //if (preset) //qDebug() << "found stored preset " << preset->name() << "for" << inputDevice.uniqueTabletId(); //else //qDebug() << "no preset fcound for" << inputDevice.uniqueTabletId(); } if (!preset) { preset = rserver->resourceByName("Basic_tip_default"); } if (preset) { //qDebug() << "inputdevicechanged 1" << preset; setCurrentPaintop(preset); } } else { if (toolData->preset) { //qDebug() << "inputdevicechanged 2" << toolData->preset; setCurrentPaintop(toolData->preset); } else { //qDebug() << "inputdevicechanged 3" << toolData->paintOpID; setCurrentPaintop(toolData->paintOpID); } } } void KisPaintopBox::slotCanvasResourceChanged(int key, const QVariant &value) { if (m_viewManager) { sender()->blockSignals(true); KisPaintOpPresetSP preset = m_viewManager->resourceProvider()->resourceManager()->resource(KisCanvasResourceProvider::CurrentPaintOpPreset).value(); if (preset && m_resourceProvider->currentPreset()->name() != preset->name()) { QString compositeOp = preset->settings()->getString("CompositeOp"); updateCompositeOp(compositeOp); resourceSelected(preset.data()); } /** * Update currently selected preset in both the popup widgets */ m_presetsChooserPopup->canvasResourceChanged(preset); m_presetsPopup->currentPresetChanged(preset); if (key == KisCanvasResourceProvider::CurrentCompositeOp) { if (m_resourceProvider->currentCompositeOp() != m_currCompositeOpID) { updateCompositeOp(m_resourceProvider->currentCompositeOp()); } } if (key == KisCanvasResourceProvider::Size) { setSliderValue("size", m_resourceProvider->size()); } if (key == KisCanvasResourceProvider::Opacity) { setSliderValue("opacity", m_resourceProvider->opacity()); } if (key == KisCanvasResourceProvider::Flow) { setSliderValue("flow", m_resourceProvider->flow()); } if (key == KisCanvasResourceProvider::EraserMode) { m_eraseAction->setChecked(value.toBool()); } if (key == KisCanvasResourceProvider::DisablePressure) { m_disablePressureAction->setChecked(value.toBool()); } sender()->blockSignals(false); } } void KisPaintopBox::slotSaveActivePreset() { KisPaintOpPresetSP curPreset = m_resourceProvider->currentPreset(); - if (!curPreset) return; - m_favoriteResourceManager->setBlockUpdates(true); - KisPaintOpPresetSP newPreset = curPreset->clone(); + KisPaintOpPresetSP oldPreset = curPreset->clone(); + oldPreset->load(); KisPaintOpPresetResourceServer * rServer = KisResourceServerProvider::instance()->paintOpPresetServer(); QString saveLocation = rServer->saveLocation(); QString presetName = m_presetsPopup->getPresetName(); - QString presetFilename = saveLocation + presetName + newPreset->defaultFileExtension(); - - QStringList tags; - KisPaintOpPresetSP resource = rServer->resourceByName(presetName); - if (resource) { - - tags = rServer->assignedTagsList(resource.data()); - rServer->removeResourceAndBlacklist(resource); - + QString currentPresetFileName = saveLocation + presetName + curPreset->defaultFileExtension(); + if (rServer->resourceByName(presetName)) { + QString currentDate = QDate::currentDate().toString(Qt::ISODate); + QString currentTime = QTime::currentTime().toString(Qt::ISODate); + QString presetFilename = saveLocation + presetName + "_backup_" + currentDate + "-" + currentTime + oldPreset->defaultFileExtension(); + oldPreset->setFilename(presetFilename); + oldPreset->setName(presetName); + oldPreset->setPresetDirty(false); + oldPreset->setValid(true); + rServer->addResource(oldPreset); + QStringList tags; + tags = rServer->assignedTagsList(curPreset.data()); + rServer->removeResourceAndBlacklist(oldPreset.data()); + Q_FOREACH (const QString & tag, tags) { + rServer->addTag(oldPreset.data(), tag); + } } - newPreset->setImage(m_presetsPopup->cutOutOverlay()); - newPreset->setFilename(presetFilename); - newPreset->setName(presetName); - newPreset->setPresetDirty(false); - - rServer->addResource(newPreset); - Q_FOREACH (const QString & tag, tags) { - rServer->addTag(newPreset.data(), tag); + if (curPreset->name()==presetName) { + if (curPreset->filename().contains(saveLocation)==false || curPreset->filename().contains(presetName)==false) { + rServer->removeResourceAndBlacklist(curPreset.data()); + curPreset->setFilename(currentPresetFileName); + curPreset->setName(presetName); + } + if (!rServer->resourceByFilename(curPreset->filename())){ + //this is necessary so that we can get the preset afterwards. + rServer->addResource(curPreset, false, false); + rServer->removeFromBlacklist(curPreset.data()); + } + curPreset->setImage(m_presetsPopup->cutOutOverlay()); + curPreset->save(); + curPreset->load(); + } else { + KisPaintOpPresetSP newPreset = curPreset->clone(); + newPreset->setFilename(currentPresetFileName); + newPreset->setName(presetName); + newPreset->setImage(m_presetsPopup->cutOutOverlay()); + newPreset->setPresetDirty(false); + newPreset->setValid(true); + rServer->addResource(newPreset); + curPreset = newPreset; //to load the new preset } // HACK ALERT! the server does not notify the observers // automatically, so we need to call theupdate manually! rServer->tagCategoryMembersChanged(); - restoreResource(newPreset.data()); + restoreResource(curPreset.data()); m_favoriteResourceManager->setBlockUpdates(false); } void KisPaintopBox::slotUpdatePreset() { if (!m_resourceProvider->currentPreset()) return; // block updates of avoid some over updating of the option widget m_blockUpdate = true; setSliderValue("size", m_resourceProvider->size()); { qreal opacity = m_resourceProvider->currentPreset()->settings()->paintOpOpacity(); m_resourceProvider->setOpacity(opacity); setSliderValue("opacity", opacity); setWidgetState(ENABLE_OPACITY); } { setSliderValue("flow", m_resourceProvider->currentPreset()->settings()->paintOpFlow()); setWidgetState(ENABLE_FLOW); } { updateCompositeOp(m_resourceProvider->currentPreset()->settings()->paintOpCompositeOp()); setWidgetState(ENABLE_COMPOSITEOP); } m_blockUpdate = false; } void KisPaintopBox::slotSetupDefaultPreset() { KisPaintOpPresetSP preset = defaultPreset(m_resourceProvider->currentPreset()->paintOp()); preset->setOptionsWidget(m_optionWidget); m_resourceProvider->setPaintOpPreset(preset); } void KisPaintopBox::slotNodeChanged(const KisNodeSP node) { if (m_previousNode.isValid() && m_previousNode->paintDevice()) disconnect(m_previousNode->paintDevice().data(), SIGNAL(colorSpaceChanged(const KoColorSpace*)), this, SLOT(slotColorSpaceChanged(const KoColorSpace*))); // Reconnect colorspace change of node if (node && node->paintDevice()) { connect(node->paintDevice().data(), SIGNAL(colorSpaceChanged(const KoColorSpace*)), this, SLOT(slotColorSpaceChanged(const KoColorSpace*))); m_resourceProvider->setCurrentCompositeOp(m_currCompositeOpID); m_previousNode = node; slotColorSpaceChanged(node->colorSpace()); } if (m_optionWidget) { m_optionWidget->setNode(node); } } void KisPaintopBox::slotColorSpaceChanged(const KoColorSpace* colorSpace) { m_cmbCompositeOp->validate(colorSpace); } void KisPaintopBox::slotToggleEraseMode(bool checked) { const bool oldEraserMode = m_resourceProvider->eraserMode(); m_resourceProvider->setEraserMode(checked); if (oldEraserMode != checked && m_eraserBrushSizeEnabled) { const qreal currentSize = m_resourceProvider->size(); KisPaintOpSettingsSP settings = m_resourceProvider->currentPreset()->settings(); // remember brush size. set the eraser size to the normal brush size if not set if (checked) { settings->setSavedBrushSize(currentSize); if (qFuzzyIsNull(settings->savedEraserSize())) { settings->setSavedEraserSize(currentSize); } } else { settings->setSavedEraserSize(currentSize); if (qFuzzyIsNull(settings->savedBrushSize())) { settings->setSavedBrushSize(currentSize); } } //update value in UI (this is the main place the value is 'stored' in memory) qreal newSize = checked ? settings->savedEraserSize() : settings->savedBrushSize(); m_resourceProvider->setSize(newSize); } if (oldEraserMode != checked && m_eraserBrushOpacityEnabled) { const qreal currentOpacity = m_resourceProvider->opacity(); KisPaintOpSettingsSP settings = m_resourceProvider->currentPreset()->settings(); // remember brush opacity. set the eraser opacity to the normal brush opacity if not set if (checked) { settings->setSavedBrushOpacity(currentOpacity); if (qFuzzyIsNull(settings->savedEraserOpacity())) { settings->setSavedEraserOpacity(currentOpacity); } } else { settings->setSavedEraserOpacity(currentOpacity); if (qFuzzyIsNull(settings->savedBrushOpacity())) { settings->setSavedBrushOpacity(currentOpacity); } } //update value in UI (this is the main place the value is 'stored' in memory) qreal newOpacity = checked ? settings->savedEraserOpacity() : settings->savedBrushOpacity(); m_resourceProvider->setOpacity(newOpacity); } } void KisPaintopBox::slotSetCompositeMode(int index) { Q_UNUSED(index); QString compositeOp = m_cmbCompositeOp->selectedCompositeOp().id(); m_resourceProvider->setCurrentCompositeOp(compositeOp); } void KisPaintopBox::slotHorizontalMirrorChanged(bool value) { m_resourceProvider->setMirrorHorizontal(value); } void KisPaintopBox::slotVerticalMirrorChanged(bool value) { m_resourceProvider->setMirrorVertical(value); } void KisPaintopBox::sliderChanged(int n) { if (!m_optionWidget) // widget will not exist if the are no documents open return; KisSignalsBlocker blocker(m_optionWidget); qreal opacity = m_sliderChooser[n]->getWidget("opacity")->value(); qreal flow = m_sliderChooser[n]->getWidget("flow")->value(); qreal size = m_sliderChooser[n]->getWidget("size")->value(); setSliderValue("opacity", opacity); setSliderValue("flow" , flow); setSliderValue("size" , size); if (m_presetsEnabled) { // IMPORTANT: set the PaintOp size before setting the other properties // it wont work the other way // TODO: why?! m_resourceProvider->setSize(size); m_resourceProvider->setOpacity(opacity); m_resourceProvider->setFlow(flow); KisLockedPropertiesProxySP propertiesProxy = KisLockedPropertiesServer::instance()->createLockedPropertiesProxy(m_resourceProvider->currentPreset()->settings()); propertiesProxy->setProperty("OpacityValue", opacity); propertiesProxy->setProperty("FlowValue", flow); m_optionWidget->setConfigurationSafe(m_resourceProvider->currentPreset()->settings().data()); } else { m_resourceProvider->setOpacity(opacity); } m_presetsPopup->resourceSelected(m_resourceProvider->currentPreset().data()); } void KisPaintopBox::slotSlider1Changed() { sliderChanged(0); } void KisPaintopBox::slotSlider2Changed() { sliderChanged(1); } void KisPaintopBox::slotSlider3Changed() { sliderChanged(2); } void KisPaintopBox::slotToolChanged(KoCanvasController* canvas, int toolId) { Q_UNUSED(canvas); Q_UNUSED(toolId); if (!m_viewManager->canvasBase()) return; QString id = KoToolManager::instance()->activeToolId(); KisTool* tool = dynamic_cast(KoToolManager::instance()->toolById(m_viewManager->canvasBase(), id)); if (tool) { int flags = tool->flags(); if (flags & KisTool::FLAG_USES_CUSTOM_COMPOSITEOP) { setWidgetState(ENABLE_COMPOSITEOP | ENABLE_OPACITY); } else { setWidgetState(DISABLE_COMPOSITEOP | DISABLE_OPACITY); } if (flags & KisTool::FLAG_USES_CUSTOM_PRESET) { setWidgetState(ENABLE_PRESETS); slotUpdatePreset(); m_presetsEnabled = true; } else { setWidgetState(DISABLE_PRESETS); m_presetsEnabled = false; } if (flags & KisTool::FLAG_USES_CUSTOM_SIZE) { setWidgetState(ENABLE_SIZE | ENABLE_FLOW); } else { setWidgetState(DISABLE_SIZE | DISABLE_FLOW); } } else setWidgetState(DISABLE_ALL); } void KisPaintopBox::slotPreviousFavoritePreset() { if (!m_favoriteResourceManager) return; int i = 0; Q_FOREACH (KisPaintOpPresetSP preset, m_favoriteResourceManager->favoritePresetList()) { if (m_resourceProvider->currentPreset() && m_resourceProvider->currentPreset()->name() == preset->name()) { if (i > 0) { m_favoriteResourceManager->slotChangeActivePaintop(i - 1); } else { m_favoriteResourceManager->slotChangeActivePaintop(m_favoriteResourceManager->numFavoritePresets() - 1); } return; } i++; } } void KisPaintopBox::slotNextFavoritePreset() { if (!m_favoriteResourceManager) return; int i = 0; Q_FOREACH (KisPaintOpPresetSP preset, m_favoriteResourceManager->favoritePresetList()) { if (m_resourceProvider->currentPreset()->name() == preset->name()) { if (i < m_favoriteResourceManager->numFavoritePresets() - 1) { m_favoriteResourceManager->slotChangeActivePaintop(i + 1); } else { m_favoriteResourceManager->slotChangeActivePaintop(0); } return; } i++; } } void KisPaintopBox::slotSwitchToPreviousPreset() { if (m_resourceProvider->previousPreset()) { //qDebug() << "slotSwitchToPreviousPreset();" << m_resourceProvider->previousPreset(); setCurrentPaintop(m_resourceProvider->previousPreset()); } } void KisPaintopBox::slotUnsetEraseMode() { m_eraseAction->setChecked(false); } void KisPaintopBox::slotToggleAlphaLockMode(bool checked) { if (checked) { m_alphaLockButton->actions()[0]->setIcon(KisIconUtils::loadIcon("transparency-locked")); } else { m_alphaLockButton->actions()[0]->setIcon(KisIconUtils::loadIcon("transparency-unlocked")); } m_resourceProvider->setGlobalAlphaLock(checked); } void KisPaintopBox::slotDisablePressureMode(bool checked) { if (checked) { m_disablePressureButton->actions()[0]->setIcon(KisIconUtils::loadIcon("transform_icons_penPressure")); } else { m_disablePressureButton->actions()[0]->setIcon(KisIconUtils::loadIcon("transform_icons_penPressure_locked")); } m_resourceProvider->setDisablePressure(checked); } void KisPaintopBox::slotReloadPreset() { KisSignalsBlocker blocker(m_optionWidget); //Here using the name and fetching the preset from the server was the only way the load was working. Otherwise it was not loading. KisPaintOpPresetResourceServer * rserver = KisResourceServerProvider::instance()->paintOpPresetServer(); KisPaintOpPresetSP preset = rserver->resourceByName(m_resourceProvider->currentPreset()->name()); if (preset) { preset->load(); } } void KisPaintopBox::slotGuiChangedCurrentPreset() // Called only when UI is changed and not when preset is changed { KisPaintOpPresetSP preset = m_resourceProvider->currentPreset(); { /** * Here we postpone all the settings updates events until thye entire writing * operation will be finished. As soon as it is finished, the updates will be * emitted happily (if there were any). */ KisPaintOpPreset::UpdatedPostponer postponer(preset.data()); m_optionWidget->writeConfigurationSafe(const_cast(preset->settings().data())); } // we should also update the preset strip to update the status of the "dirty" mark m_presetsPopup->resourceSelected(m_resourceProvider->currentPreset().data()); // TODO!!!!!!!! //m_presetsPopup->updateViewSettings(); } void KisPaintopBox::slotSaveLockedOptionToPreset(KisPropertiesConfigurationSP p) { QMapIterator i(p->getProperties()); while (i.hasNext()) { i.next(); m_resourceProvider->currentPreset()->settings()->setProperty(i.key(), QVariant(i.value())); if (m_resourceProvider->currentPreset()->settings()->hasProperty(i.key() + "_previous")) { m_resourceProvider->currentPreset()->settings()->removeProperty(i.key() + "_previous"); } } slotGuiChangedCurrentPreset(); } void KisPaintopBox::slotDropLockedOption(KisPropertiesConfigurationSP p) { KisSignalsBlocker blocker(m_optionWidget); KisPaintOpPresetSP preset = m_resourceProvider->currentPreset(); { KisPaintOpPreset::DirtyStateSaver dirtySaver(preset.data()); QMapIterator i(p->getProperties()); while (i.hasNext()) { i.next(); if (preset->settings()->hasProperty(i.key() + "_previous")) { preset->settings()->setProperty(i.key(), preset->settings()->getProperty(i.key() + "_previous")); preset->settings()->removeProperty(i.key() + "_previous"); } } } //slotUpdatePreset(); } void KisPaintopBox::slotDirtyPresetToggled(bool value) { if (!value) { slotReloadPreset(); m_presetsPopup->resourceSelected(m_resourceProvider->currentPreset().data()); m_presetsPopup->updateViewSettings(); } m_dirtyPresetsEnabled = value; KisConfig cfg; cfg.setUseDirtyPresets(m_dirtyPresetsEnabled); } void KisPaintopBox::slotEraserBrushSizeToggled(bool value) { m_eraserBrushSizeEnabled = value; KisConfig cfg; cfg.setUseEraserBrushSize(m_eraserBrushSizeEnabled); } void KisPaintopBox::slotEraserBrushOpacityToggled(bool value) { m_eraserBrushOpacityEnabled = value; KisConfig cfg; cfg.setUseEraserBrushOpacity(m_eraserBrushOpacityEnabled); } void KisPaintopBox::slotUpdateSelectionIcon() { m_hMirrorAction->setIcon(KisIconUtils::loadIcon("symmetry-horizontal")); m_vMirrorAction->setIcon(KisIconUtils::loadIcon("symmetry-vertical")); KisConfig cfg; if (!cfg.toolOptionsInDocker() && m_toolOptionsPopupButton) { m_toolOptionsPopupButton->setIcon(KisIconUtils::loadIcon("configure")); } m_presetSelectorPopupButton->setIcon(KisIconUtils::loadIcon("paintop_settings_01")); m_brushEditorPopupButton->setIcon(KisIconUtils::loadIcon("paintop_settings_02")); m_workspaceWidget->setIcon(KisIconUtils::loadIcon("view-choose")); m_eraseAction->setIcon(KisIconUtils::loadIcon("draw-eraser")); m_reloadAction->setIcon(KisIconUtils::loadIcon("view-refresh")); if (m_disablePressureAction->isChecked()) { m_disablePressureButton->setIcon(KisIconUtils::loadIcon("transform_icons_penPressure")); } else { m_disablePressureButton->setIcon(KisIconUtils::loadIcon("transform_icons_penPressure_locked")); } } void KisPaintopBox::slotLockXMirrorToggle(bool toggleLock) { m_resourceProvider->setMirrorHorizontalLock(toggleLock); } void KisPaintopBox::slotLockYMirrorToggle(bool toggleLock) { m_resourceProvider->setMirrorVerticalLock(toggleLock); } void KisPaintopBox::slotHideDecorationMirrorX(bool toggled) { m_resourceProvider->setMirrorHorizontalHideDecorations(toggled); } void KisPaintopBox::slotHideDecorationMirrorY(bool toggled) { m_resourceProvider->setMirrorVerticalHideDecorations(toggled); } void KisPaintopBox::slotMoveToCenterMirrorX() { m_resourceProvider->mirrorHorizontalMoveCanvasToCenter(); } void KisPaintopBox::slotMoveToCenterMirrorY() { m_resourceProvider->mirrorVerticalMoveCanvasToCenter(); } diff --git a/libs/ui/kis_popup_palette.cpp b/libs/ui/kis_popup_palette.cpp index e89c2bb620..85749a004c 100644 --- a/libs/ui/kis_popup_palette.cpp +++ b/libs/ui/kis_popup_palette.cpp @@ -1,908 +1,917 @@ /* This file is part of the KDE project Copyright 2009 Vera Lukman Copyright 2011 Sven Langkamp Copyright 2016 Scott Petrovic This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_config.h" #include "kis_popup_palette.h" #include "kis_paintop_box.h" #include "kis_favorite_resource_manager.h" #include "kis_icon_utils.h" #include #include "kis_resource_server_provider.h" #include #include #include #include #include "KoColorSpaceRegistry.h" #include #include #include #include #include #include #include #include #include #include #include #include "kis_signal_compressor.h" #include #include "brushhud/kis_brush_hud.h" #include "brushhud/kis_round_hud_button.h" #include class PopupColorTriangle : public KoTriangleColorSelector { public: PopupColorTriangle(const KoColorDisplayRendererInterface *displayRenderer, QWidget* parent) : KoTriangleColorSelector(displayRenderer, parent) , m_dragging(false) { } ~PopupColorTriangle() override {} void tabletEvent(QTabletEvent* event) override { event->accept(); QMouseEvent* mouseEvent = 0; switch (event->type()) { case QEvent::TabletPress: mouseEvent = new QMouseEvent(QEvent::MouseButtonPress, event->pos(), Qt::LeftButton, Qt::LeftButton, event->modifiers()); m_dragging = true; mousePressEvent(mouseEvent); break; case QEvent::TabletMove: mouseEvent = new QMouseEvent(QEvent::MouseMove, event->pos(), (m_dragging) ? Qt::LeftButton : Qt::NoButton, (m_dragging) ? Qt::LeftButton : Qt::NoButton, event->modifiers()); mouseMoveEvent(mouseEvent); break; case QEvent::TabletRelease: mouseEvent = new QMouseEvent(QEvent::MouseButtonRelease, event->pos(), Qt::LeftButton, Qt::LeftButton, event->modifiers()); m_dragging = false; mouseReleaseEvent(mouseEvent); break; default: break; } delete mouseEvent; } private: bool m_dragging; }; KisPopupPalette::KisPopupPalette(KisViewManager* viewManager, KisCoordinatesConverter* coordinatesConverter ,KisFavoriteResourceManager* manager, const KoColorDisplayRendererInterface *displayRenderer, KisCanvasResourceProvider *provider, QWidget *parent) : QWidget(parent, Qt::FramelessWindowHint) , m_hoveredPreset(0) , m_hoveredColor(0) , m_selectedColor(0) , m_coordinatesConverter(coordinatesConverter) , m_actionManager(viewManager->actionManager()) , m_resourceManager(manager) , m_triangleColorSelector(0) , m_displayRenderer(displayRenderer) , m_colorChangeCompressor(new KisSignalCompressor(50, KisSignalCompressor::POSTPONE)) , m_actionCollection(viewManager->actionCollection()) , m_brushHud(0) , m_popupPaletteSize(385.0) , m_colorHistoryInnerRadius(72.0) , m_colorHistoryOuterRadius(92.0) , m_isOverCanvasRotationIndicator(false) , m_isRotatingCanvasIndicator(false) { // some UI controls are defined and created based off these variables const int borderWidth = 3; if (KisConfig().readEntry("popuppalette/usevisualcolorselector", false)) { m_triangleColorSelector = new KisVisualColorSelector(this); } else { m_triangleColorSelector = new PopupColorTriangle(displayRenderer, this); } m_triangleColorSelector->setDisplayRenderer(displayRenderer); m_triangleColorSelector->setConfig(true,false); m_triangleColorSelector->move(m_popupPaletteSize/2-m_colorHistoryInnerRadius+borderWidth, m_popupPaletteSize/2-m_colorHistoryInnerRadius+borderWidth); m_triangleColorSelector->resize(m_colorHistoryInnerRadius*2-borderWidth*2, m_colorHistoryInnerRadius*2-borderWidth*2); m_triangleColorSelector->setVisible(true); KoColor fgcolor(Qt::black, KoColorSpaceRegistry::instance()->rgb8()); if (m_resourceManager) { fgcolor = provider->fgColor(); } m_triangleColorSelector->slotSetColor(fgcolor); QRegion maskedRegion(0, 0, m_triangleColorSelector->width(), m_triangleColorSelector->height(), QRegion::Ellipse ); m_triangleColorSelector->setMask(maskedRegion); //setAttribute(Qt::WA_TranslucentBackground, true); connect(m_triangleColorSelector, SIGNAL(sigNewColor(const KoColor &)), m_colorChangeCompressor.data(), SLOT(start())); connect(m_colorChangeCompressor.data(), SIGNAL(timeout()), SLOT(slotEmitColorChanged())); connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), m_triangleColorSelector, SLOT(configurationChanged())); connect(m_resourceManager, SIGNAL(sigChangeFGColorSelector(KoColor)), SLOT(slotExternalFgColorChanged(KoColor))); connect(this, SIGNAL(sigChangefGColor(KoColor)), m_resourceManager, SIGNAL(sigSetFGColor(KoColor))); connect(this, SIGNAL(sigChangeActivePaintop(int)), m_resourceManager, SLOT(slotChangeActivePaintop(int))); connect(this, SIGNAL(sigUpdateRecentColor(int)), m_resourceManager, SLOT(slotUpdateRecentColor(int))); connect(m_resourceManager, SIGNAL(setSelectedColor(int)), SLOT(slotSetSelectedColor(int))); connect(m_resourceManager, SIGNAL(updatePalettes()), SLOT(slotUpdate())); connect(m_resourceManager, SIGNAL(hidePalettes()), SLOT(slotHide())); // This is used to handle a bug: // If pop up palette is visible and a new colour is selected, the new colour // will be added when the user clicks on the canvas to hide the palette // In general, we want to be able to store recent color if the pop up palette // is not visible m_timer.setSingleShot(true); connect(this, SIGNAL(sigTriggerTimer()), this, SLOT(slotTriggerTimer())); connect(&m_timer, SIGNAL(timeout()), this, SLOT(slotEnableChangeFGColor())); connect(this, SIGNAL(sigEnableChangeFGColor(bool)), m_resourceManager, SIGNAL(sigEnableChangeColor(bool))); setCursor(Qt::ArrowCursor); setMouseTracking(true); setHoveredPreset(-1); setHoveredColor(-1); setSelectedColor(-1); m_brushHud = new KisBrushHud(provider, parent); m_brushHud->setMaximumHeight(m_popupPaletteSize); m_brushHud->setVisible(false); const int auxButtonSize = 35; m_settingsButton = new KisRoundHudButton(this); m_settingsButton->setIcon(KisIconUtils::loadIcon("configure")); m_settingsButton->setGeometry(m_popupPaletteSize - 2.2 * auxButtonSize, m_popupPaletteSize - auxButtonSize, auxButtonSize, auxButtonSize); connect(m_settingsButton, SIGNAL(clicked()), SLOT(slotShowTagsPopup())); KisConfig cfg; m_brushHudButton = new KisRoundHudButton(this); m_brushHudButton->setCheckable(true); m_brushHudButton->setOnOffIcons(KisIconUtils::loadIcon("arrow-left"), KisIconUtils::loadIcon("arrow-right")); m_brushHudButton->setGeometry(m_popupPaletteSize - 1.0 * auxButtonSize, m_popupPaletteSize - auxButtonSize, auxButtonSize, auxButtonSize); connect(m_brushHudButton, SIGNAL(toggled(bool)), SLOT(showHudWidget(bool))); m_brushHudButton->setChecked(cfg.showBrushHud()); // add some stuff below the pop-up palette that will make it easier to use for tablet people QVBoxLayout* vLayout = new QVBoxLayout(this); // main layout QSpacerItem* verticalSpacer = new QSpacerItem(0, 0, QSizePolicy::Fixed, QSizePolicy::Expanding); vLayout->addSpacerItem(verticalSpacer); // this should push the box to the bottom QHBoxLayout* hLayout = new QHBoxLayout(); vLayout->addLayout(hLayout); mirrorMode = new KisHighlightedToolButton(this); mirrorMode->setCheckable(true); mirrorMode->setFixedSize(35, 35); mirrorMode->setIcon(KisIconUtils::loadIcon("symmetry-horizontal")); mirrorMode->setToolTip(i18n("Mirror Canvas")); connect(mirrorMode, SIGNAL(clicked(bool)), this, SLOT(slotmirroModeClicked())); canvasOnlyButton = new KisHighlightedToolButton(this); canvasOnlyButton->setCheckable(true); canvasOnlyButton->setFixedSize(35, 35); canvasOnlyButton->setIcon(KisIconUtils::loadIcon("document-new")); canvasOnlyButton->setToolTip(i18n("Canvas Only")); connect(canvasOnlyButton, SIGNAL(clicked(bool)), this, SLOT(slotCanvasonlyModeClicked())); zoomToOneHundredPercentButton = new QPushButton(this); zoomToOneHundredPercentButton->setText(i18n("100%")); zoomToOneHundredPercentButton->setFixedHeight(35); zoomToOneHundredPercentButton->setIcon(KisIconUtils::loadIcon("zoom-original")); zoomToOneHundredPercentButton->setToolTip(i18n("Zoom to 100%")); connect(zoomToOneHundredPercentButton, SIGNAL(clicked(bool)), this, SLOT(slotZoomToOneHundredPercentClicked())); zoomCanvasSlider = new QSlider(Qt::Horizontal, this); - zoomCanvasSlider->setRange(10, 200); // 10% to 200 % + zoomSliderMinValue = 10; // set in % + zoomSliderMaxValue = 200; // set in % + + zoomCanvasSlider->setRange(zoomSliderMinValue, zoomSliderMaxValue); zoomCanvasSlider->setFixedHeight(35); zoomCanvasSlider->setValue(m_coordinatesConverter->zoomInPercent()); zoomCanvasSlider->setSingleStep(1); zoomCanvasSlider->setPageStep(1); connect(zoomCanvasSlider, SIGNAL(valueChanged(int)), this, SLOT(slotZoomSliderChanged(int))); hLayout->addWidget(mirrorMode); hLayout->addWidget(canvasOnlyButton); hLayout->addWidget(zoomToOneHundredPercentButton); hLayout->addWidget(zoomCanvasSlider); setVisible(true); setVisible(false); } void KisPopupPalette::slotExternalFgColorChanged(const KoColor &color) { //hack to get around cmyk for now. if (color.colorSpace()->colorChannelCount()>3) { KoColor c(KoColorSpaceRegistry::instance()->rgb8()); c.fromKoColor(color); m_triangleColorSelector->slotSetColor(c); } else { m_triangleColorSelector->slotSetColor(color); } } void KisPopupPalette::slotEmitColorChanged() { if (isVisible()) { update(); emit sigChangefGColor(m_triangleColorSelector->getCurrentColor()); } } //setting KisPopupPalette properties int KisPopupPalette::hoveredPreset() const { return m_hoveredPreset; } void KisPopupPalette::setHoveredPreset(int x) { m_hoveredPreset = x; } int KisPopupPalette::hoveredColor() const { return m_hoveredColor; } void KisPopupPalette::setHoveredColor(int x) { m_hoveredColor = x; } int KisPopupPalette::selectedColor() const { return m_selectedColor; } void KisPopupPalette::setSelectedColor(int x) { m_selectedColor = x; } void KisPopupPalette::slotTriggerTimer() { m_timer.start(750); } void KisPopupPalette::slotEnableChangeFGColor() { emit sigEnableChangeFGColor(true); } void KisPopupPalette::slotZoomSliderChanged(int zoom) { emit zoomLevelChanged(zoom); } void KisPopupPalette::adjustLayout(const QPoint &p) { KIS_ASSERT_RECOVER_RETURN(m_brushHud); if (isVisible() && parentWidget()) { float hudMargin = 30.0; const QRect fitRect = kisGrowRect(parentWidget()->rect(), -20.0); // -20 is widget margin const QPoint paletteCenterOffset(m_popupPaletteSize / 2, m_popupPaletteSize / 2); QRect paletteRect = rect(); paletteRect.moveTo(p - paletteCenterOffset); if (m_brushHudButton->isChecked()) { m_brushHud->updateGeometry(); paletteRect.adjust(0, 0, m_brushHud->width() + hudMargin, 0); } paletteRect = kisEnsureInRect(paletteRect, fitRect); move(paletteRect.topLeft()); m_brushHud->move(paletteRect.topLeft() + QPoint(m_popupPaletteSize + hudMargin, 0)); m_lastCenterPoint = p; } } void KisPopupPalette::showHudWidget(bool visible) { KIS_ASSERT_RECOVER_RETURN(m_brushHud); const bool reallyVisible = visible && m_brushHudButton->isChecked(); if (reallyVisible) { m_brushHud->updateProperties(); } m_brushHud->setVisible(reallyVisible); adjustLayout(m_lastCenterPoint); KisConfig cfg; cfg.setShowBrushHud(visible); } void KisPopupPalette::showPopupPalette(const QPoint &p) { showPopupPalette(!isVisible()); adjustLayout(p); } void KisPopupPalette::showPopupPalette(bool show) { if (show) { - zoomCanvasSlider->setValue(m_coordinatesConverter->zoomInPercent()); // sync the zoom slider + + // don't set the zoom slider if we are outside of the zoom slider bounds. It will change the zoom level to within + // the bounds and cause the canvas to jump between the slider's min and max + if (m_coordinatesConverter->zoomInPercent() > zoomSliderMinValue && + m_coordinatesConverter->zoomInPercent() < zoomSliderMaxValue ){ + zoomCanvasSlider->setValue(m_coordinatesConverter->zoomInPercent()); // sync the zoom slider + } + emit sigEnableChangeFGColor(!show); } else { emit sigTriggerTimer(); } setVisible(show); m_brushHud->setVisible(show && m_brushHudButton->isChecked()); } //redefinition of setVariable function to change the scope to private void KisPopupPalette::setVisible(bool b) { QWidget::setVisible(b); } void KisPopupPalette::setParent(QWidget *parent) { m_brushHud->setParent(parent); QWidget::setParent(parent); } QSize KisPopupPalette::sizeHint() const { return QSize(m_popupPaletteSize, m_popupPaletteSize + 50); // last number is the space for the toolbar below } void KisPopupPalette::resizeEvent(QResizeEvent*) { } void KisPopupPalette::paintEvent(QPaintEvent* e) { Q_UNUSED(e); QPainter painter(this); QPen pen(palette().color(QPalette::Text)); pen.setWidth(3); painter.setPen(pen); painter.setRenderHint(QPainter::Antialiasing); painter.setRenderHint(QPainter::SmoothPixmapTransform); //painting background color indicator QPainterPath bgColor; bgColor.addEllipse(QPoint( 50, 80), 30, 30); painter.fillPath(bgColor, m_displayRenderer->toQColor(m_resourceManager->bgColor())); painter.drawPath(bgColor); //painting foreground color indicator QPainterPath fgColor; fgColor.addEllipse(QPoint( 60, 50), 30, 30); painter.fillPath(fgColor, m_displayRenderer->toQColor(m_triangleColorSelector->getCurrentColor())); painter.drawPath(fgColor); // create a circle background that everything else will go into QPainterPath backgroundContainer; float shrinkCircleAmount = 3;// helps the circle when the stroke is put around it QRectF circleRect(shrinkCircleAmount, shrinkCircleAmount, m_popupPaletteSize - shrinkCircleAmount*2,m_popupPaletteSize - shrinkCircleAmount*2); backgroundContainer.addEllipse( circleRect ); painter.fillPath(backgroundContainer,palette().brush(QPalette::Background)); painter.drawPath(backgroundContainer); // create a path slightly inside the container circle. this will create a 'track' to indicate that we can rotate the canvas // with the indicator QPainterPath rotationTrackPath; shrinkCircleAmount = 18; QRectF circleRect2(shrinkCircleAmount, shrinkCircleAmount, m_popupPaletteSize - shrinkCircleAmount*2,m_popupPaletteSize - shrinkCircleAmount*2); rotationTrackPath.addEllipse( circleRect2 ); pen.setWidth(1); painter.setPen(pen); painter.drawPath(rotationTrackPath); // this thing will help indicate where the starting brush preset is at. // also what direction they go to give sor order to the presets populated /* pen.setWidth(6); pen.setCapStyle(Qt::RoundCap); painter.setPen(pen); painter.drawArc(circleRect, (16*90), (16*-30)); // span angle (last parameter) is in 16th of degrees QPainterPath brushDir; brushDir.arcMoveTo(circleRect, 60); brushDir.lineTo(brushDir.currentPosition().x()-5, brushDir.currentPosition().y() - 14); painter.drawPath(brushDir); brushDir.lineTo(brushDir.currentPosition().x()-2, brushDir.currentPosition().y() + 6); painter.drawPath(brushDir); */ // the following things needs to be based off the center, so let's translate the painter painter.translate(m_popupPaletteSize / 2, m_popupPaletteSize / 2); // create the canvas rotation handle QPainterPath rotationIndicator = drawRotationIndicator(m_coordinatesConverter->rotationAngle(), true); painter.fillPath(rotationIndicator,palette().brush(QPalette::Text)); // hover indicator for the canvas rotation if (m_isOverCanvasRotationIndicator == true) { painter.save(); QPen pen(palette().color(QPalette::Highlight)); pen.setWidth(2); painter.setPen(pen); painter.drawPath(rotationIndicator); painter.restore(); } // create a reset canvas rotation indicator to bring the canvas back to 0 degrees QPainterPath resetRotationIndicator = drawRotationIndicator(0, false); QPen resetPen(palette().color(QPalette::Text)); resetPen.setWidth(1); painter.save(); painter.setPen(resetPen); painter.drawPath(resetRotationIndicator); painter.restore(); //painting favorite brushes QList images(m_resourceManager->favoritePresetImages()); //painting favorite brushes pixmap/icon QPainterPath presetPath; for (int pos = 0; pos < numSlots(); pos++) { painter.save(); presetPath = createPathFromPresetIndex(pos); if (pos < images.size()) { painter.setClipPath(presetPath); QRect bounds = presetPath.boundingRect().toAlignedRect(); painter.drawImage(bounds.topLeft() , images.at(pos).scaled(bounds.size() , Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation)); } else { painter.fillPath(presetPath, palette().brush(QPalette::Window)); // brush slot that has no brush in it } QPen pen = painter.pen(); pen.setWidth(1); painter.setPen(pen); painter.drawPath(presetPath); painter.restore(); } if (hoveredPreset() > -1) { presetPath = createPathFromPresetIndex(hoveredPreset()); QPen pen(palette().color(QPalette::Highlight)); pen.setWidth(3); painter.setPen(pen); painter.drawPath(presetPath); } // paint recent colors area. painter.setPen(Qt::NoPen); float rotationAngle = -360.0 / m_resourceManager->recentColorsTotal(); // there might be no recent colors at the start, so paint a placeholder if (m_resourceManager->recentColorsTotal() == 0) { painter.setBrush(Qt::transparent); QPainterPath emptyRecentColorsPath(drawDonutPathFull(0, 0, m_colorHistoryInnerRadius, m_colorHistoryOuterRadius)); painter.setPen(QPen(palette().color(QPalette::Background).lighter(150), 2, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin)); painter.drawPath(emptyRecentColorsPath); } else { for (int pos = 0; pos < m_resourceManager->recentColorsTotal(); pos++) { QPainterPath recentColorsPath(drawDonutPathAngle(m_colorHistoryInnerRadius, m_colorHistoryOuterRadius, m_resourceManager->recentColorsTotal())); //accessing recent color of index pos painter.fillPath(recentColorsPath, m_displayRenderer->toQColor( m_resourceManager->recentColorAt(pos) )); painter.drawPath(recentColorsPath); painter.rotate(rotationAngle); } } //painting hovered color if (hoveredColor() > -1) { painter.setPen(QPen(palette().color(QPalette::Highlight), 2, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin)); if (m_resourceManager->recentColorsTotal() == 1) { QPainterPath path_ColorDonut(drawDonutPathFull(0, 0, m_colorHistoryInnerRadius, m_colorHistoryOuterRadius)); painter.drawPath(path_ColorDonut); } else { painter.rotate((m_resourceManager->recentColorsTotal() + hoveredColor()) *rotationAngle); QPainterPath path(drawDonutPathAngle(m_colorHistoryInnerRadius, m_colorHistoryOuterRadius, m_resourceManager->recentColorsTotal())); painter.drawPath(path); painter.rotate(hoveredColor() * -1 * rotationAngle); } } //painting selected color if (selectedColor() > -1) { painter.setPen(QPen(palette().color(QPalette::Highlight).darker(130), 2, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin)); if (m_resourceManager->recentColorsTotal() == 1) { QPainterPath path_ColorDonut(drawDonutPathFull(0, 0, m_colorHistoryInnerRadius, m_colorHistoryOuterRadius)); painter.drawPath(path_ColorDonut); } else { painter.rotate((m_resourceManager->recentColorsTotal() + selectedColor()) *rotationAngle); QPainterPath path(drawDonutPathAngle(m_colorHistoryInnerRadius, m_colorHistoryOuterRadius, m_resourceManager->recentColorsTotal())); painter.drawPath(path); painter.rotate(selectedColor() * -1 * rotationAngle); } } } QPainterPath KisPopupPalette::drawDonutPathFull(int x, int y, int inner_radius, int outer_radius) { QPainterPath path; path.addEllipse(QPointF(x, y), outer_radius, outer_radius); path.addEllipse(QPointF(x, y), inner_radius, inner_radius); path.setFillRule(Qt::OddEvenFill); return path; } QPainterPath KisPopupPalette::drawDonutPathAngle(int inner_radius, int outer_radius, int limit) { QPainterPath path; path.moveTo(-0.999 * outer_radius * sin(M_PI / limit), 0.999 * outer_radius * cos(M_PI / limit)); path.arcTo(-1 * outer_radius, -1 * outer_radius, 2 * outer_radius, 2 * outer_radius, -90.0 - 180.0 / limit, 360.0 / limit); path.arcTo(-1 * inner_radius, -1 * inner_radius, 2 * inner_radius, 2 * inner_radius, -90.0 + 180.0 / limit, - 360.0 / limit); path.closeSubpath(); return path; } QPainterPath KisPopupPalette::drawRotationIndicator(qreal rotationAngle, bool canDrag) { // used for canvas rotation. This function gets called twice. Once by the canvas rotation indicator, // and another time by the reset canvas position float canvasRotationRadians = qDegreesToRadians(rotationAngle - 90); // -90 will make 0 degrees be at the top float rotationDialXPosition = qCos(canvasRotationRadians) * (m_popupPaletteSize/2 - 10); // m_popupPaletteSize/2 = radius float rotationDialYPosition = qSin(canvasRotationRadians) * (m_popupPaletteSize/2 - 10); QPainterPath canvasRotationIndicator; int canvasIndicatorSize = 15; float canvasIndicatorMiddle = canvasIndicatorSize/2; QRect indicatorRectangle = QRect( rotationDialXPosition - canvasIndicatorMiddle, rotationDialYPosition - canvasIndicatorMiddle, canvasIndicatorSize, canvasIndicatorSize ); if (canDrag) { m_canvasRotationIndicatorRect = indicatorRectangle; } else { m_resetCanvasRotationIndicatorRect = indicatorRectangle; } canvasRotationIndicator.addEllipse(indicatorRectangle.x(), indicatorRectangle.y(), indicatorRectangle.width(), indicatorRectangle.height() ); return canvasRotationIndicator; } void KisPopupPalette::mouseMoveEvent(QMouseEvent* event) { QPointF point = event->posF(); event->accept(); setToolTip(QString()); setHoveredPreset(-1); setHoveredColor(-1); // calculate if we are over the canvas rotation knob // before we started painting, we moved the painter to the center of the widget, so the X/Y positions are offset. we need to // correct them first before looking for a click event intersection float rotationCorrectedXPos = m_canvasRotationIndicatorRect.x() + (m_popupPaletteSize / 2); float rotationCorrectedYPos = m_canvasRotationIndicatorRect.y() + (m_popupPaletteSize / 2); QRect correctedCanvasRotationIndicator = QRect(rotationCorrectedXPos, rotationCorrectedYPos, m_canvasRotationIndicatorRect.width(), m_canvasRotationIndicatorRect.height()); if (correctedCanvasRotationIndicator.contains(point.x(), point.y())) { m_isOverCanvasRotationIndicator = true; } else { m_isOverCanvasRotationIndicator = false; } if (m_isRotatingCanvasIndicator) { // we are rotating the canvas, so calculate the rotation angle based off the center // calculate the angle we are at first QPoint widgetCenterPoint = QPoint(m_popupPaletteSize/2, m_popupPaletteSize/2); float dX = point.x() - widgetCenterPoint.x(); float dY = point.y() - widgetCenterPoint.y(); float finalAngle = qAtan2(dY,dX) * 180 / M_PI; // what we need if we have two points, but don't know the angle finalAngle = finalAngle + 90; // add 90 degrees so 0 degree position points up float angleDifference = finalAngle - m_coordinatesConverter->rotationAngle(); // the rotation function accepts diffs, so find it out m_coordinatesConverter->rotate(m_coordinatesConverter->widgetCenterPoint(), angleDifference); emit sigUpdateCanvas(); } // don't highlight the presets if we are in the middle of rotating the canvas if (m_isRotatingCanvasIndicator == false) { QPainterPath pathColor(drawDonutPathFull(m_popupPaletteSize / 2, m_popupPaletteSize / 2, m_colorHistoryInnerRadius, m_colorHistoryOuterRadius)); { int pos = calculatePresetIndex(point, m_resourceManager->numFavoritePresets()); if (pos >= 0 && pos < m_resourceManager->numFavoritePresets()) { setToolTip(m_resourceManager->favoritePresetList().at(pos).data()->name()); setHoveredPreset(pos); } } if (pathColor.contains(point)) { int pos = calculateIndex(point, m_resourceManager->recentColorsTotal()); if (pos >= 0 && pos < m_resourceManager->recentColorsTotal()) { setHoveredColor(pos); } } } update(); } void KisPopupPalette::mousePressEvent(QMouseEvent* event) { QPointF point = event->posF(); event->accept(); if (event->button() == Qt::LeftButton) { //in favorite brushes area int pos = calculateIndex(point, m_resourceManager->numFavoritePresets()); if (pos >= 0 && pos < m_resourceManager->numFavoritePresets() && isPointInPixmap(point, pos)) { //setSelectedBrush(pos); update(); } if (m_isOverCanvasRotationIndicator) { m_isRotatingCanvasIndicator = true; } // reset the canvas if we are over the reset canvas rotation indicator float rotationCorrectedXPos = m_resetCanvasRotationIndicatorRect.x() + (m_popupPaletteSize / 2); float rotationCorrectedYPos = m_resetCanvasRotationIndicatorRect.y() + (m_popupPaletteSize / 2); QRect correctedResetCanvasRotationIndicator = QRect(rotationCorrectedXPos, rotationCorrectedYPos, m_resetCanvasRotationIndicatorRect.width(), m_resetCanvasRotationIndicatorRect.height()); if (correctedResetCanvasRotationIndicator.contains(point.x(), point.y())) { float angleDifference = -m_coordinatesConverter->rotationAngle(); // the rotation function accepts diffs, so find it ou m_coordinatesConverter->rotate(m_coordinatesConverter->widgetCenterPoint(), angleDifference); emit sigUpdateCanvas(); } } } void KisPopupPalette::slotShowTagsPopup() { KisPaintOpPresetResourceServer* rServer = KisResourceServerProvider::instance()->paintOpPresetServer(); QStringList tags = rServer->tagNamesList(); qSort(tags); if (!tags.isEmpty()) { QMenu menu; Q_FOREACH (const QString& tag, tags) { menu.addAction(tag); } QAction* action = menu.exec(QCursor::pos()); if (action) { m_resourceManager->setCurrentTag(action->text()); } } else { QWhatsThis::showText(QCursor::pos(), i18n("There are no tags available to show in this popup. To add presets, you need to tag them and then select the tag here.")); } } void KisPopupPalette::slotmirroModeClicked() { QAction* action = m_actionCollection->action("mirror_canvas"); if (action) { action->trigger(); } } void KisPopupPalette::slotCanvasonlyModeClicked() { QAction* action = m_actionCollection->action("view_show_canvas_only"); if (action) { action->trigger(); } } void KisPopupPalette::slotZoomToOneHundredPercentClicked() { QAction* action = m_actionCollection->action("zoom_to_100pct"); if (action) { action->trigger(); } // also move the zoom slider to 100% position so they are in sync zoomCanvasSlider->setValue(100); } void KisPopupPalette::tabletEvent(QTabletEvent* /*event*/) { } void KisPopupPalette::mouseReleaseEvent(QMouseEvent * event) { QPointF point = event->posF(); event->accept(); m_isOverCanvasRotationIndicator = false; m_isRotatingCanvasIndicator = false; if (event->button() == Qt::LeftButton || event->button() == Qt::RightButton) { QPainterPath pathColor(drawDonutPathFull(m_popupPaletteSize / 2, m_popupPaletteSize / 2, m_colorHistoryInnerRadius, m_colorHistoryOuterRadius)); //in favorite brushes area if (hoveredPreset() > -1) { //setSelectedBrush(hoveredBrush()); emit sigChangeActivePaintop(hoveredPreset()); } if (pathColor.contains(point)) { int pos = calculateIndex(point, m_resourceManager->recentColorsTotal()); if (pos >= 0 && pos < m_resourceManager->recentColorsTotal()) { emit sigUpdateRecentColor(pos); } } } } int KisPopupPalette::calculateIndex(QPointF point, int n) { calculatePresetIndex(point, n); //translate to (0,0) point.setX(point.x() - m_popupPaletteSize / 2); point.setY(point.y() - m_popupPaletteSize / 2); //rotate float smallerAngle = M_PI / 2 + M_PI / n - atan2(point.y(), point.x()); float radius = sqrt((float)point.x() * point.x() + point.y() * point.y()); point.setX(radius * cos(smallerAngle)); point.setY(radius * sin(smallerAngle)); //calculate brush index int pos = floor(acos(point.x() / radius) * n / (2 * M_PI)); if (point.y() < 0) pos = n - pos - 1; return pos; } bool KisPopupPalette::isPointInPixmap(QPointF& point, int pos) { if (createPathFromPresetIndex(pos).contains(point + QPointF(-m_popupPaletteSize / 2, -m_popupPaletteSize / 2))) { return true; } return false; } KisPopupPalette::~KisPopupPalette() { } QPainterPath KisPopupPalette::createPathFromPresetIndex(int index) { qreal angleSlice = 360.0 / numSlots() ; // how many degrees each slice will get // the starting angle of the slice we need to draw. the negative sign makes us go clockwise. // adding 90 degrees makes us start at the top. otherwise we would start at the right qreal startingAngle = -(index * angleSlice) + 90; // the radius will get smaller as the amount of presets shown increases. 10 slots == 41 qreal presetRadius = m_colorHistoryOuterRadius * qSin(qDegreesToRadians(angleSlice/2)) / (1-qSin(qDegreesToRadians(angleSlice/2))); QPainterPath path; float pathX = (m_colorHistoryOuterRadius + presetRadius) * qCos(qDegreesToRadians(startingAngle)) - presetRadius; float pathY = -(m_colorHistoryOuterRadius + presetRadius) * qSin(qDegreesToRadians(startingAngle)) - presetRadius; float pathDiameter = 2 * presetRadius; // distance is used to calculate the X/Y in addition to the preset circle size path.addEllipse(pathX, pathY, pathDiameter, pathDiameter); return path; } int KisPopupPalette::calculatePresetIndex(QPointF point, int /*n*/) { for(int i = 0; i < numSlots(); i++) { QPointF adujustedPoint = point - QPointF(m_popupPaletteSize/2, m_popupPaletteSize/2); if(createPathFromPresetIndex(i).contains(adujustedPoint)) { return i; } } return -1; } int KisPopupPalette::numSlots() { KisConfig config; return qMax(config.favoritePresets(), 10); } - diff --git a/libs/ui/kis_popup_palette.h b/libs/ui/kis_popup_palette.h index df6c4c16c2..241cd6d246 100644 --- a/libs/ui/kis_popup_palette.h +++ b/libs/ui/kis_popup_palette.h @@ -1,176 +1,178 @@ /* This file is part of the KDE project Copyright 2009 Vera Lukman Copyright 2016 Scott Petrovic This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_POPUP_PALETTE_H #define KIS_POPUP_PALETTE_H #include #include #include #include #include #include #include "kis_action_manager.h" #include "KisViewManager.h" #include "kactioncollection.h" #include "kis_coordinates_converter.h" #include "kis_tool_button.h" #include "kis_highlighted_button.h" #include class KisFavoriteResourceManager; class QWidget; class KoColor; class KoTriangleColorSelector; class KisSignalCompressor; class KisBrushHud; class KisRoundHudButton; class KisCanvasResourceProvider; class KisVisualColorSelector; class KisPopupPalette : public QWidget { Q_OBJECT Q_PROPERTY(int hoveredPreset READ hoveredPreset WRITE setHoveredPreset) Q_PROPERTY(int hoveredColor READ hoveredColor WRITE setHoveredColor) Q_PROPERTY(int selectedColor READ selectedColor WRITE setSelectedColor) public: KisPopupPalette(KisViewManager*, KisCoordinatesConverter* ,KisFavoriteResourceManager*, const KoColorDisplayRendererInterface *displayRenderer, KisCanvasResourceProvider *provider, QWidget *parent = 0); ~KisPopupPalette() override; QSize sizeHint() const override; void showPopupPalette(const QPoint&); void showPopupPalette(bool b); //functions to set up selectedBrush void setSelectedBrush(int x); int selectedBrush() const; //functions to set up selectedColor void setSelectedColor(int x); int selectedColor() const; void setParent(QWidget *parent); void tabletEvent(QTabletEvent * event) override; protected: void paintEvent(QPaintEvent*) override; void resizeEvent(QResizeEvent*) override; void mouseReleaseEvent(QMouseEvent*) override; void mouseMoveEvent(QMouseEvent*) override; void mousePressEvent(QMouseEvent*) override; //functions to calculate index of favorite brush or recent color in array //n is the total number of favorite brushes or recent colors int calculateIndex(QPointF, int n); int calculatePresetIndex(QPointF, int n); //functions to set up hoveredBrush void setHoveredPreset(int x); int hoveredPreset() const; //functions to set up hoveredColor void setHoveredColor(int x); int hoveredColor() const; private: void setVisible(bool b) override; QPainterPath drawDonutPathFull(int, int, int, int); QPainterPath drawDonutPathAngle(int, int, int); QPainterPath drawRotationIndicator(qreal rotationAngle, bool canDrag); bool isPointInPixmap(QPointF&, int pos); QPainterPath createPathFromPresetIndex(int index); int numSlots(); void adjustLayout(const QPoint &p); private: int m_hoveredPreset; int m_hoveredColor; int m_selectedColor; KisCoordinatesConverter* m_coordinatesConverter; KisActionManager* m_actionManager; KisFavoriteResourceManager* m_resourceManager; KisColorSelectorInterface* m_triangleColorSelector {0}; const KoColorDisplayRendererInterface *m_displayRenderer; QScopedPointer m_colorChangeCompressor; KActionCollection* m_actionCollection; QTimer m_timer; KisBrushHud *m_brushHud; float m_popupPaletteSize; float m_colorHistoryInnerRadius; float m_colorHistoryOuterRadius; KisRoundHudButton *m_settingsButton; KisRoundHudButton *m_brushHudButton; QPoint m_lastCenterPoint; QRect m_canvasRotationIndicatorRect; QRect m_resetCanvasRotationIndicatorRect; bool m_isOverCanvasRotationIndicator; bool m_isRotatingCanvasIndicator; KisHighlightedToolButton* mirrorMode; KisHighlightedToolButton* canvasOnlyButton; QPushButton* zoomToOneHundredPercentButton; QSlider* zoomCanvasSlider; + int zoomSliderMinValue; + int zoomSliderMaxValue; Q_SIGNALS: void sigChangeActivePaintop(int); void sigUpdateRecentColor(int); void sigChangefGColor(const KoColor&); void sigUpdateCanvas(); void zoomLevelChanged(int); // These are used to handle a bug: // If pop up palette is visible and a new colour is selected, the new colour // will be added when the user clicks on the canvas to hide the palette // In general, we want to be able to store recent color if the pop up palette // is not visible void sigEnableChangeFGColor(bool); void sigTriggerTimer(); private Q_SLOTS: void slotExternalFgColorChanged(const KoColor &color); void slotEmitColorChanged(); void slotSetSelectedColor(int x) { setSelectedColor(x); update(); } void slotTriggerTimer(); void slotEnableChangeFGColor(); void slotUpdate() { update(); } void slotHide() { showPopupPalette(false); } void slotShowTagsPopup(); void showHudWidget(bool visible); void slotmirroModeClicked(); void slotCanvasonlyModeClicked(); void slotZoomToOneHundredPercentClicked(); void slotZoomSliderChanged(int zoom); }; #endif // KIS_POPUP_PALETTE_H diff --git a/libs/ui/tool/kis_resources_snapshot.cpp b/libs/ui/tool/kis_resources_snapshot.cpp index 971b6d76d5..dc20e63113 100644 --- a/libs/ui/tool/kis_resources_snapshot.cpp +++ b/libs/ui/tool/kis_resources_snapshot.cpp @@ -1,389 +1,394 @@ /* * Copyright (c) 2011 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_resources_snapshot.h" #include #include #include #include #include #include #include #include #include "kis_canvas_resource_provider.h" #include "filter/kis_filter_configuration.h" #include "kis_image.h" #include "kis_paint_device.h" #include "kis_paint_layer.h" #include "recorder/kis_recorded_paint_action.h" #include "kis_selection.h" #include "kis_selection_mask.h" #include "kis_algebra_2d.h" struct KisResourcesSnapshot::Private { Private() : currentPattern(0) , currentGradient(0) , currentGenerator(0) , compositeOp(0) { } KisImageSP image; KisDefaultBoundsBaseSP bounds; KoColor currentFgColor; KoColor currentBgColor; KoPattern *currentPattern; KoAbstractGradient *currentGradient; KisPaintOpPresetSP currentPaintOpPreset; KisNodeSP currentNode; qreal currentExposure; KisFilterConfigurationSP currentGenerator; QPointF axesCenter; bool mirrorMaskHorizontal; bool mirrorMaskVertical; quint8 opacity; QString compositeOpId; const KoCompositeOp *compositeOp; KisPainter::StrokeStyle strokeStyle; KisPainter::FillStyle fillStyle; bool globalAlphaLock; qreal effectiveZoom; bool presetAllowsLod; KisSelectionSP selectionOverride; }; KisResourcesSnapshot::KisResourcesSnapshot(KisImageSP image, KisNodeSP currentNode, KoCanvasResourceManager *resourceManager, KisDefaultBoundsBaseSP bounds) : m_d(new Private()) { m_d->image = image; if (!bounds) { bounds = new KisDefaultBounds(m_d->image); } m_d->bounds = bounds; m_d->currentFgColor = resourceManager->resource(KoCanvasResourceManager::ForegroundColor).value(); m_d->currentBgColor = resourceManager->resource(KoCanvasResourceManager::BackgroundColor).value(); m_d->currentPattern = resourceManager->resource(KisCanvasResourceProvider::CurrentPattern).value(); m_d->currentGradient = resourceManager->resource(KisCanvasResourceProvider::CurrentGradient).value(); /** * We should deep-copy the preset, so that long-runnign actions * will have correct brush parameters. Theoretically this cloniong * can be expensive, but according to measurements, it takes * something like 0.1 ms for an average preset. */ m_d->currentPaintOpPreset = resourceManager->resource(KisCanvasResourceProvider::CurrentPaintOpPreset).value()->clone(); #ifdef HAVE_THREADED_TEXT_RENDERING_WORKAROUND KisPaintOpRegistry::instance()->preinitializePaintOpIfNeeded(m_d->currentPaintOpPreset); #endif /* HAVE_THREADED_TEXT_RENDERING_WORKAROUND */ m_d->currentExposure = resourceManager->resource(KisCanvasResourceProvider::HdrExposure).toDouble(); m_d->currentGenerator = resourceManager->resource(KisCanvasResourceProvider::CurrentGeneratorConfiguration).value(); QPointF relativeAxesCenter(0.5, 0.5); if (m_d->image) { relativeAxesCenter = m_d->image->mirrorAxesCenter(); } m_d->axesCenter = KisAlgebra2D::relativeToAbsolute(relativeAxesCenter, m_d->bounds->bounds()); m_d->mirrorMaskHorizontal = resourceManager->resource(KisCanvasResourceProvider::MirrorHorizontal).toBool(); m_d->mirrorMaskVertical = resourceManager->resource(KisCanvasResourceProvider::MirrorVertical).toBool(); qreal normOpacity = resourceManager->resource(KisCanvasResourceProvider::Opacity).toDouble(); m_d->opacity = quint8(normOpacity * OPACITY_OPAQUE_U8); m_d->compositeOpId = resourceManager->resource(KisCanvasResourceProvider::CurrentEffectiveCompositeOp).toString(); setCurrentNode(currentNode); /** * Fill and Stroke styles are not a part of the resource manager * so the tools should set them manually * TODO: port stroke and fill styles to be a part * of the resource manager */ m_d->strokeStyle = KisPainter::StrokeStyleBrush; m_d->fillStyle = KisPainter::FillStyleNone; m_d->globalAlphaLock = resourceManager->resource(KisCanvasResourceProvider::GlobalAlphaLock).toBool(); m_d->effectiveZoom = resourceManager->resource(KisCanvasResourceProvider::EffectiveZoom).toDouble(); m_d->presetAllowsLod = resourceManager->resource(KisCanvasResourceProvider::PresetAllowsLod).toBool(); } KisResourcesSnapshot::KisResourcesSnapshot(KisImageSP image, KisNodeSP currentNode, KisDefaultBoundsBaseSP bounds) : m_d(new Private()) { m_d->image = image; if (!bounds) { bounds = new KisDefaultBounds(m_d->image); } m_d->bounds = bounds; #ifdef HAVE_THREADED_TEXT_RENDERING_WORKAROUND KisPaintOpRegistry::instance()->preinitializePaintOpIfNeeded(m_d->currentPaintOpPreset); #endif /* HAVE_THREADED_TEXT_RENDERING_WORKAROUND */ QPointF relativeAxesCenter(0.5, 0.5); if (m_d->image) { relativeAxesCenter = m_d->image->mirrorAxesCenter(); } m_d->axesCenter = KisAlgebra2D::relativeToAbsolute(relativeAxesCenter, m_d->bounds->bounds()); m_d->opacity = OPACITY_OPAQUE_U8; setCurrentNode(currentNode); /** * Fill and Stroke styles are not a part of the resource manager * so the tools should set them manually * TODO: port stroke and fill styles to be a part * of the resource manager */ m_d->strokeStyle = KisPainter::StrokeStyleBrush; m_d->fillStyle = KisPainter::FillStyleNone; } KisResourcesSnapshot::~KisResourcesSnapshot() { delete m_d; } void KisResourcesSnapshot::setupPainter(KisPainter* painter) { painter->setPaintColor(m_d->currentFgColor); painter->setBackgroundColor(m_d->currentBgColor); painter->setGenerator(m_d->currentGenerator); painter->setPattern(m_d->currentPattern); painter->setGradient(m_d->currentGradient); QBitArray lockflags = channelLockFlags(); if (lockflags.size() > 0) { painter->setChannelFlags(lockflags); } painter->setOpacity(m_d->opacity); painter->setCompositeOp(m_d->compositeOp); painter->setMirrorInformation(m_d->axesCenter, m_d->mirrorMaskHorizontal, m_d->mirrorMaskVertical); painter->setStrokeStyle(m_d->strokeStyle); painter->setFillStyle(m_d->fillStyle); /** * The paintOp should be initialized the last, because it may * ask the painter for some options while initialization */ painter->setPaintOpPreset(m_d->currentPaintOpPreset, m_d->currentNode, m_d->image); } void KisResourcesSnapshot::setupPaintAction(KisRecordedPaintAction *action) { action->setPaintOpPreset(m_d->currentPaintOpPreset); action->setPaintIncremental(!needsIndirectPainting()); action->setPaintColor(m_d->currentFgColor); action->setBackgroundColor(m_d->currentBgColor); action->setGenerator(m_d->currentGenerator); action->setGradient(m_d->currentGradient); action->setPattern(m_d->currentPattern); action->setOpacity(m_d->opacity / qreal(OPACITY_OPAQUE_U8)); action->setCompositeOp(m_d->compositeOp->id()); action->setStrokeStyle(m_d->strokeStyle); action->setFillStyle(m_d->fillStyle); } KisPostExecutionUndoAdapter* KisResourcesSnapshot::postExecutionUndoAdapter() const { return m_d->image ? m_d->image->postExecutionUndoAdapter() : 0; } void KisResourcesSnapshot::setCurrentNode(KisNodeSP node) { m_d->currentNode = node; KisPaintDeviceSP device; if(m_d->currentNode && (device = m_d->currentNode->paintDevice())) { m_d->compositeOp = device->colorSpace()->compositeOp(m_d->compositeOpId); if(!m_d->compositeOp) { m_d->compositeOp = device->colorSpace()->compositeOp(COMPOSITE_OVER); } } } void KisResourcesSnapshot::setStrokeStyle(KisPainter::StrokeStyle strokeStyle) { m_d->strokeStyle = strokeStyle; } void KisResourcesSnapshot::setFillStyle(KisPainter::FillStyle fillStyle) { m_d->fillStyle = fillStyle; } KisNodeSP KisResourcesSnapshot::currentNode() const { return m_d->currentNode; } KisImageSP KisResourcesSnapshot::image() const { return m_d->image; } bool KisResourcesSnapshot::needsIndirectPainting() const { return !m_d->currentPaintOpPreset->settings()->paintIncremental(); } QString KisResourcesSnapshot::indirectPaintingCompositeOp() const { return m_d->currentPaintOpPreset->settings()->indirectPaintingCompositeOp(); } KisSelectionSP KisResourcesSnapshot::activeSelection() const { /** * It is possible to have/use the snapshot without the image. Such * usecase is present for example in the scratchpad. */ if (m_d->selectionOverride) { return m_d->selectionOverride; } KisSelectionSP selection = m_d->image ? m_d->image->globalSelection() : 0; KisLayerSP layer = qobject_cast(m_d->currentNode.data()); KisSelectionMaskSP mask; if((layer = qobject_cast(m_d->currentNode.data()))) { selection = layer->selection(); } else if ((mask = dynamic_cast(m_d->currentNode.data())) && mask->selection() == selection) { selection = 0; } return selection; } bool KisResourcesSnapshot::needsAirbrushing() const { return m_d->currentPaintOpPreset->settings()->isAirbrushing(); } qreal KisResourcesSnapshot::airbrushingInterval() const { return m_d->currentPaintOpPreset->settings()->airbrushInterval(); } +bool KisResourcesSnapshot::needsSpacingUpdates() const +{ + return m_d->currentPaintOpPreset->settings()->useSpacingUpdates(); +} + void KisResourcesSnapshot::setOpacity(qreal opacity) { m_d->opacity = opacity * OPACITY_OPAQUE_U8; } quint8 KisResourcesSnapshot::opacity() const { return m_d->opacity; } const KoCompositeOp* KisResourcesSnapshot::compositeOp() const { return m_d->compositeOp; } QString KisResourcesSnapshot::compositeOpId() const { return m_d->compositeOpId; } KoPattern* KisResourcesSnapshot::currentPattern() const { return m_d->currentPattern; } KoColor KisResourcesSnapshot::currentFgColor() const { return m_d->currentFgColor; } KoColor KisResourcesSnapshot::currentBgColor() const { return m_d->currentBgColor; } KisPaintOpPresetSP KisResourcesSnapshot::currentPaintOpPreset() const { return m_d->currentPaintOpPreset; } QBitArray KisResourcesSnapshot::channelLockFlags() const { QBitArray channelFlags; KisPaintLayer *paintLayer; if ((paintLayer = dynamic_cast(m_d->currentNode.data()))) { channelFlags = paintLayer->channelLockFlags(); if (m_d->globalAlphaLock) { if (channelFlags.isEmpty()) { channelFlags = paintLayer->colorSpace()->channelFlags(true, true); } channelFlags &= paintLayer->colorSpace()->channelFlags(true, false); } } return channelFlags; } qreal KisResourcesSnapshot::effectiveZoom() const { return m_d->effectiveZoom; } bool KisResourcesSnapshot::presetAllowsLod() const { return m_d->presetAllowsLod; } void KisResourcesSnapshot::setFGColorOverride(const KoColor &color) { m_d->currentFgColor = color; } void KisResourcesSnapshot::setBGColorOverride(const KoColor &color) { m_d->currentBgColor = color; } void KisResourcesSnapshot::setSelectionOverride(KisSelectionSP selection) { m_d->selectionOverride = selection; } void KisResourcesSnapshot::setBrush(const KisPaintOpPresetSP &brush) { m_d->currentPaintOpPreset = brush; } diff --git a/libs/ui/tool/kis_resources_snapshot.h b/libs/ui/tool/kis_resources_snapshot.h index 08d6c968ca..dede9ba147 100644 --- a/libs/ui/tool/kis_resources_snapshot.h +++ b/libs/ui/tool/kis_resources_snapshot.h @@ -1,104 +1,106 @@ /* * Copyright (c) 2011 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef __KIS_RESOURCES_SNAPSHOT_H #define __KIS_RESOURCES_SNAPSHOT_H #include "kis_shared.h" #include "kis_shared_ptr.h" #include "kis_types.h" #include "kritaui_export.h" #include "kis_painter.h" #include "kis_default_bounds.h" class KoCanvasResourceManager; class KoCompositeOp; class KisPainter; class KisPostExecutionUndoAdapter; class KisRecordedPaintAction; class KoPattern; /** * @brief The KisResourcesSnapshot class takes a snapshot of the various resources * like colors and settings used at the begin of a stroke or a recording so subsequent * changes don't impact the running stroke. The main reason for the snapshot is that the * user can *change* the options while the stroke is being executed in the background. */ class KRITAUI_EXPORT KisResourcesSnapshot : public KisShared { public: KisResourcesSnapshot(KisImageSP image, KisNodeSP currentNode, KoCanvasResourceManager *resourceManager, KisDefaultBoundsBaseSP bounds = 0); KisResourcesSnapshot(KisImageSP image, KisNodeSP currentNode, KisDefaultBoundsBaseSP bounds = 0); ~KisResourcesSnapshot(); void setupPainter(KisPainter *painter); // XXX: This was marked as KDE_DEPRECATED, but no althernative was // given in the apidox. void setupPaintAction(KisRecordedPaintAction *action); KisPostExecutionUndoAdapter* postExecutionUndoAdapter() const; void setCurrentNode(KisNodeSP node); void setStrokeStyle(KisPainter::StrokeStyle strokeStyle); void setFillStyle(KisPainter::FillStyle fillStyle); KisNodeSP currentNode() const; KisImageSP image() const; bool needsIndirectPainting() const; QString indirectPaintingCompositeOp() const; /** * \return currently active selection. Note that it will return * null if current node *is* the current selection. This * is done to avoid recursive selection application when * painting on selectgion masks. */ KisSelectionSP activeSelection() const; bool needsAirbrushing() const; qreal airbrushingInterval() const; + bool needsSpacingUpdates() const; + void setOpacity(qreal opacity); quint8 opacity() const; const KoCompositeOp* compositeOp() const; QString compositeOpId() const; KoPattern* currentPattern() const; KoColor currentFgColor() const; KoColor currentBgColor() const; KisPaintOpPresetSP currentPaintOpPreset() const; /// @return the channel lock flags of the current node with the global override applied QBitArray channelLockFlags() const; qreal effectiveZoom() const; bool presetAllowsLod() const; void setFGColorOverride(const KoColor &color); void setBGColorOverride(const KoColor &color); void setSelectionOverride(KisSelectionSP selection); void setBrush(const KisPaintOpPresetSP &brush); private: struct Private; Private * const m_d; }; typedef KisSharedPtr KisResourcesSnapshotSP; #endif /* __KIS_RESOURCES_SNAPSHOT_H */ diff --git a/libs/ui/tool/kis_tool_freehand_helper.cpp b/libs/ui/tool/kis_tool_freehand_helper.cpp index b4bc6e3317..22ccb2e61a 100644 --- a/libs/ui/tool/kis_tool_freehand_helper.cpp +++ b/libs/ui/tool/kis_tool_freehand_helper.cpp @@ -1,969 +1,974 @@ /* * Copyright (c) 2011 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_tool_freehand_helper.h" #include #include #include #include #include #include "kis_algebra_2d.h" #include "kis_distance_information.h" #include "kis_painting_information_builder.h" #include "kis_recording_adapter.h" #include "kis_image.h" #include "kis_painter.h" #include #include #include "kis_update_time_monitor.h" #include "kis_stabilized_events_sampler.h" #include "KisStabilizerDelayedPaintHelper.h" #include "kis_config.h" #include //#define DEBUG_BEZIER_CURVES // Factor by which to scale the airbrush timer's interval, relative to the actual airbrushing rate. // Setting this less than 1 makes the timer-generated pseudo-events happen faster than the desired // airbrush rate, which can improve responsiveness. const qreal AIRBRUSH_INTERVAL_FACTOR = 0.5; // The amount of time, in milliseconds, to allow between updates of the spacing information. Only -// used when airbrushing. +// used when spacing updates between dabs are enabled. const qreal SPACING_UPDATE_INTERVAL = 50.0; +// The amount of time, in milliseconds, to allow between updates of the timing information. Only +// used when airbrushing. +const qreal TIMING_UPDATE_INTERVAL = 50.0; + struct KisToolFreehandHelper::Private { KisPaintingInformationBuilder *infoBuilder; KisRecordingAdapter *recordingAdapter; KisStrokesFacade *strokesFacade; KUndo2MagicString transactionText; bool haveTangent; QPointF previousTangent; bool hasPaintAtLeastOnce; QTime strokeTime; QTimer strokeTimeoutTimer; QVector painterInfos; KisResourcesSnapshotSP resources; KisStrokeId strokeId; KisPaintInformation previousPaintInformation; KisPaintInformation olderPaintInformation; KisSmoothingOptionsSP smoothingOptions; // Timer used to generate paint updates periodically even without input events. This is only // used for paintops that depend on timely updates even when the cursor is not moving, e.g. for // airbrushing effects. QTimer airbrushingTimer; QList history; QList distanceHistory; // Keeps track of past cursor positions. This is used to determine the drawing angle when // drawing the brush outline or starting a stroke. KisPaintOpUtils::PositionHistory lastCursorPos; // Stabilizer data QQueue stabilizerDeque; QTimer stabilizerPollTimer; KisStabilizedEventsSampler stabilizedSampler; KisStabilizerDelayedPaintHelper stabilizerDelayedPaintHelper; int canvasRotation; bool canvasMirroredH; qreal effectiveSmoothnessDistance() const; }; KisToolFreehandHelper::KisToolFreehandHelper(KisPaintingInformationBuilder *infoBuilder, const KUndo2MagicString &transactionText, KisRecordingAdapter *recordingAdapter, KisSmoothingOptions *smoothingOptions) : m_d(new Private()) { m_d->infoBuilder = infoBuilder; m_d->recordingAdapter = recordingAdapter; m_d->transactionText = transactionText; m_d->smoothingOptions = KisSmoothingOptionsSP( smoothingOptions ? smoothingOptions : new KisSmoothingOptions()); m_d->canvasRotation = 0; m_d->strokeTimeoutTimer.setSingleShot(true); connect(&m_d->strokeTimeoutTimer, SIGNAL(timeout()), SLOT(finishStroke())); connect(&m_d->airbrushingTimer, SIGNAL(timeout()), SLOT(doAirbrushing())); connect(&m_d->stabilizerPollTimer, SIGNAL(timeout()), SLOT(stabilizerPollAndPaint())); m_d->stabilizerDelayedPaintHelper.setPaintLineCallback( [this](const KisPaintInformation &pi1, const KisPaintInformation &pi2) { paintLine(pi1, pi2); }); m_d->stabilizerDelayedPaintHelper.setUpdateOutlineCallback( [this]() { emit requestExplicitUpdateOutline(); }); } KisToolFreehandHelper::~KisToolFreehandHelper() { delete m_d; } void KisToolFreehandHelper::setSmoothness(KisSmoothingOptionsSP smoothingOptions) { m_d->smoothingOptions = smoothingOptions; } KisSmoothingOptionsSP KisToolFreehandHelper::smoothingOptions() const { return m_d->smoothingOptions; } QPainterPath KisToolFreehandHelper::paintOpOutline(const QPointF &savedCursorPos, const KoPointerEvent *event, const KisPaintOpSettingsSP globalSettings, KisPaintOpSettings::OutlineMode mode) const { KisPaintOpSettingsSP settings = globalSettings; KisPaintInformation info = m_d->infoBuilder->hover(savedCursorPos, event); QPointF prevPoint = m_d->lastCursorPos.pushThroughHistory(savedCursorPos); qreal startAngle = KisAlgebra2D::directionBetweenPoints(prevPoint, savedCursorPos, 0); info.setCanvasRotation(m_d->canvasRotation); info.setCanvasHorizontalMirrorState( m_d->canvasMirroredH ); KisDistanceInformation distanceInfo(prevPoint, 0, startAngle); if (!m_d->painterInfos.isEmpty()) { settings = m_d->resources->currentPaintOpPreset()->settings(); if (m_d->stabilizerDelayedPaintHelper.running() && m_d->stabilizerDelayedPaintHelper.hasLastPaintInformation()) { info = m_d->stabilizerDelayedPaintHelper.lastPaintInformation(); } else { info = m_d->previousPaintInformation; } /** * When LoD mode is active it may happen that the helper has * already started a stroke, but it painted noting, because * all the work is being calculated by the scaled-down LodN * stroke. So at first we try to fetch the data from the lodN * stroke ("buddy") and then check if there is at least * something has been painted with this distance information * object. */ KisDistanceInformation *buddyDistance = m_d->painterInfos.first()->buddyDragDistance(); if (buddyDistance) { /** * Tiny hack alert: here we fetch the distance information * directly from the LodN stroke. Ideally, we should * upscale its data, but here we just override it with our * local copy of the coordinates. */ distanceInfo = *buddyDistance; distanceInfo.overrideLastValues(prevPoint, 0, startAngle); } else if (m_d->painterInfos.first()->dragDistance->isStarted()) { distanceInfo = *m_d->painterInfos.first()->dragDistance; } } KisPaintInformation::DistanceInformationRegistrar registrar = info.registerDistanceInformation(&distanceInfo); QPainterPath outline = settings->brushOutline(info, mode); if (m_d->resources && m_d->smoothingOptions->smoothingType() == KisSmoothingOptions::STABILIZER && m_d->smoothingOptions->useDelayDistance()) { const qreal R = m_d->smoothingOptions->delayDistance() / m_d->resources->effectiveZoom(); outline.addEllipse(info.pos(), R, R); } return outline; } void KisToolFreehandHelper::cursorMoved(const QPointF &cursorPos) { m_d->lastCursorPos.pushThroughHistory(cursorPos); } void KisToolFreehandHelper::initPaint(KoPointerEvent *event, const QPointF &pixelCoords, KoCanvasResourceManager *resourceManager, KisImageWSP image, KisNodeSP currentNode, KisStrokesFacade *strokesFacade, KisNodeSP overrideNode, KisDefaultBoundsBaseSP bounds) { QPointF prevPoint = m_d->lastCursorPos.pushThroughHistory(pixelCoords); m_d->strokeTime.start(); KisPaintInformation pi = m_d->infoBuilder->startStroke(event, elapsedStrokeTime(), resourceManager); qreal startAngle = KisAlgebra2D::directionBetweenPoints(prevPoint, pixelCoords, 0.0); initPaintImpl(startAngle, pi, resourceManager, image, currentNode, strokesFacade, overrideNode, bounds); } bool KisToolFreehandHelper::isRunning() const { return m_d->strokeId; } void KisToolFreehandHelper::initPaintImpl(qreal startAngle, const KisPaintInformation &pi, KoCanvasResourceManager *resourceManager, KisImageWSP image, KisNodeSP currentNode, KisStrokesFacade *strokesFacade, KisNodeSP overrideNode, KisDefaultBoundsBaseSP bounds) { - Q_UNUSED(overrideNode); - m_d->strokesFacade = strokesFacade; m_d->haveTangent = false; m_d->previousTangent = QPointF(); m_d->hasPaintAtLeastOnce = false; m_d->previousPaintInformation = pi; - KisDistanceInitInfo startDistInfo(m_d->previousPaintInformation.pos(), - m_d->previousPaintInformation.currentTime(), - startAngle, - SPACING_UPDATE_INTERVAL); - KisDistanceInformation startDist = startDistInfo.makeDistInfo(); - - createPainters(m_d->painterInfos, - startDist); - m_d->resources = new KisResourcesSnapshot(image, currentNode, resourceManager, bounds); - if(overrideNode) { m_d->resources->setCurrentNode(overrideNode); } + const bool airbrushing = m_d->resources->needsAirbrushing(); + const bool useSpacingUpdates = m_d->resources->needsSpacingUpdates(); + + KisDistanceInitInfo startDistInfo(m_d->previousPaintInformation.pos(), + m_d->previousPaintInformation.currentTime(), + startAngle, + useSpacingUpdates ? SPACING_UPDATE_INTERVAL : LONG_TIME, + airbrushing ? TIMING_UPDATE_INTERVAL : LONG_TIME); + KisDistanceInformation startDist = startDistInfo.makeDistInfo(); + + createPainters(m_d->painterInfos, + startDist); + if(m_d->recordingAdapter) { m_d->recordingAdapter->startStroke(image, m_d->resources, startDistInfo); } KisStrokeStrategy *stroke = new FreehandStrokeStrategy(m_d->resources->needsIndirectPainting(), m_d->resources->indirectPaintingCompositeOp(), m_d->resources, m_d->painterInfos, m_d->transactionText); m_d->strokeId = m_d->strokesFacade->startStroke(stroke); m_d->history.clear(); m_d->distanceHistory.clear(); - if(m_d->resources->needsAirbrushing()) { + if(airbrushing) { m_d->airbrushingTimer.setInterval(computeAirbrushTimerInterval()); m_d->airbrushingTimer.start(); } if (m_d->smoothingOptions->smoothingType() == KisSmoothingOptions::STABILIZER) { stabilizerStart(m_d->previousPaintInformation); } // If airbrushing, paint an initial dab immediately. This is a workaround for an issue where - // some paintops (Dyna, Particle, Sketch) might never initialize their spacing information until - // paintAt is called. - if (m_d->resources->needsAirbrushing()) { + // some paintops (Dyna, Particle, Sketch) might never initialize their spacing/timing + // information until paintAt is called. + if (airbrushing) { paintAt(pi); } } void KisToolFreehandHelper::paintBezierSegment(KisPaintInformation pi1, KisPaintInformation pi2, QPointF tangent1, QPointF tangent2) { if (tangent1.isNull() || tangent2.isNull()) return; const qreal maxSanePoint = 1e6; QPointF controlTarget1; QPointF controlTarget2; // Shows the direction in which control points go QPointF controlDirection1 = pi1.pos() + tangent1; QPointF controlDirection2 = pi2.pos() - tangent2; // Lines in the direction of the control points QLineF line1(pi1.pos(), controlDirection1); QLineF line2(pi2.pos(), controlDirection2); // Lines to check whether the control points lay on the opposite // side of the line QLineF line3(controlDirection1, controlDirection2); QLineF line4(pi1.pos(), pi2.pos()); QPointF intersection; if (line3.intersect(line4, &intersection) == QLineF::BoundedIntersection) { qreal controlLength = line4.length() / 2; line1.setLength(controlLength); line2.setLength(controlLength); controlTarget1 = line1.p2(); controlTarget2 = line2.p2(); } else { QLineF::IntersectType type = line1.intersect(line2, &intersection); if (type == QLineF::NoIntersection || intersection.manhattanLength() > maxSanePoint) { intersection = 0.5 * (pi1.pos() + pi2.pos()); // dbgKrita << "WARINING: there is no intersection point " // << "in the basic smoothing algoriths"; } controlTarget1 = intersection; controlTarget2 = intersection; } // shows how near to the controlTarget the value raises qreal coeff = 0.8; qreal velocity1 = QLineF(QPointF(), tangent1).length(); qreal velocity2 = QLineF(QPointF(), tangent2).length(); if (velocity1 == 0.0 || velocity2 == 0.0) { velocity1 = 1e-6; velocity2 = 1e-6; warnKrita << "WARNING: Basic Smoothing: Velocity is Zero! Please report a bug:" << ppVar(velocity1) << ppVar(velocity2); } qreal similarity = qMin(velocity1/velocity2, velocity2/velocity1); // the controls should not differ more than 50% similarity = qMax(similarity, qreal(0.5)); // when the controls are symmetric, their size should be smaller // to avoid corner-like curves coeff *= 1 - qMax(qreal(0.0), similarity - qreal(0.8)); Q_ASSERT(coeff > 0); QPointF control1; QPointF control2; if (velocity1 > velocity2) { control1 = pi1.pos() * (1.0 - coeff) + coeff * controlTarget1; coeff *= similarity; control2 = pi2.pos() * (1.0 - coeff) + coeff * controlTarget2; } else { control2 = pi2.pos() * (1.0 - coeff) + coeff * controlTarget2; coeff *= similarity; control1 = pi1.pos() * (1.0 - coeff) + coeff * controlTarget1; } paintBezierCurve(pi1, control1, control2, pi2); } qreal KisToolFreehandHelper::Private::effectiveSmoothnessDistance() const { const qreal effectiveSmoothnessDistance = !smoothingOptions->useScalableDistance() ? smoothingOptions->smoothnessDistance() : smoothingOptions->smoothnessDistance() / resources->effectiveZoom(); return effectiveSmoothnessDistance; } void KisToolFreehandHelper::paintEvent(KoPointerEvent *event) { KisPaintInformation info = m_d->infoBuilder->continueStroke(event, elapsedStrokeTime()); info.setCanvasRotation( m_d->canvasRotation ); info.setCanvasHorizontalMirrorState( m_d->canvasMirroredH ); KisUpdateTimeMonitor::instance()->reportMouseMove(info.pos()); paint(info); } void KisToolFreehandHelper::paint(KisPaintInformation &info) { /** * Smooth the coordinates out using the history and the * distance. This is a heavily modified version of an algo used in * Gimp and described in https://bugs.kde.org/show_bug.cgi?id=281267 and * http://www24.atwiki.jp/sigetch_2007/pages/17.html. The main * differences are: * * 1) It uses 'distance' instead of 'velocity', since time * measurements are too unstable in realworld environment * * 2) There is no 'Quality' parameter, since the number of samples * is calculated automatically * * 3) 'Tail Aggressiveness' is used for controling the end of the * stroke * * 4) The formila is a little bit different: 'Distance' parameter * stands for $3 \Sigma$ */ if (m_d->smoothingOptions->smoothingType() == KisSmoothingOptions::WEIGHTED_SMOOTHING && m_d->smoothingOptions->smoothnessDistance() > 0.0) { { // initialize current distance QPointF prevPos; if (!m_d->history.isEmpty()) { const KisPaintInformation &prevPi = m_d->history.last(); prevPos = prevPi.pos(); } else { prevPos = m_d->previousPaintInformation.pos(); } qreal currentDistance = QVector2D(info.pos() - prevPos).length(); m_d->distanceHistory.append(currentDistance); } m_d->history.append(info); qreal x = 0.0; qreal y = 0.0; if (m_d->history.size() > 3) { const qreal sigma = m_d->effectiveSmoothnessDistance() / 3.0; // '3.0' for (3 * sigma) range qreal gaussianWeight = 1 / (sqrt(2 * M_PI) * sigma); qreal gaussianWeight2 = sigma * sigma; qreal distanceSum = 0.0; qreal scaleSum = 0.0; qreal pressure = 0.0; qreal baseRate = 0.0; Q_ASSERT(m_d->history.size() == m_d->distanceHistory.size()); for (int i = m_d->history.size() - 1; i >= 0; i--) { qreal rate = 0.0; const KisPaintInformation nextInfo = m_d->history.at(i); double distance = m_d->distanceHistory.at(i); Q_ASSERT(distance >= 0.0); qreal pressureGrad = 0.0; if (i < m_d->history.size() - 1) { pressureGrad = nextInfo.pressure() - m_d->history.at(i + 1).pressure(); const qreal tailAgressiveness = 40.0 * m_d->smoothingOptions->tailAggressiveness(); if (pressureGrad > 0.0 ) { pressureGrad *= tailAgressiveness * (1.0 - nextInfo.pressure()); distance += pressureGrad * 3.0 * sigma; // (3 * sigma) --- holds > 90% of the region } } if (gaussianWeight2 != 0.0) { distanceSum += distance; rate = gaussianWeight * exp(-distanceSum * distanceSum / (2 * gaussianWeight2)); } if (m_d->history.size() - i == 1) { baseRate = rate; } else if (baseRate / rate > 100) { break; } scaleSum += rate; x += rate * nextInfo.pos().x(); y += rate * nextInfo.pos().y(); if (m_d->smoothingOptions->smoothPressure()) { pressure += rate * nextInfo.pressure(); } } if (scaleSum != 0.0) { x /= scaleSum; y /= scaleSum; if (m_d->smoothingOptions->smoothPressure()) { pressure /= scaleSum; } } if ((x != 0.0 && y != 0.0) || (x == info.pos().x() && y == info.pos().y())) { info.setPos(QPointF(x, y)); if (m_d->smoothingOptions->smoothPressure()) { info.setPressure(pressure); } m_d->history.last() = info; } } } if (m_d->smoothingOptions->smoothingType() == KisSmoothingOptions::SIMPLE_SMOOTHING || m_d->smoothingOptions->smoothingType() == KisSmoothingOptions::WEIGHTED_SMOOTHING) { // Now paint between the coordinates, using the bezier curve interpolation if (!m_d->haveTangent) { m_d->haveTangent = true; m_d->previousTangent = (info.pos() - m_d->previousPaintInformation.pos()) / qMax(qreal(1.0), info.currentTime() - m_d->previousPaintInformation.currentTime()); } else { QPointF newTangent = (info.pos() - m_d->olderPaintInformation.pos()) / qMax(qreal(1.0), info.currentTime() - m_d->olderPaintInformation.currentTime()); if (newTangent.isNull() || m_d->previousTangent.isNull()) { paintLine(m_d->previousPaintInformation, info); } else { paintBezierSegment(m_d->olderPaintInformation, m_d->previousPaintInformation, m_d->previousTangent, newTangent); } m_d->previousTangent = newTangent; } m_d->olderPaintInformation = m_d->previousPaintInformation; m_d->strokeTimeoutTimer.start(100); } else if (m_d->smoothingOptions->smoothingType() == KisSmoothingOptions::NO_SMOOTHING){ paintLine(m_d->previousPaintInformation, info); } if (m_d->smoothingOptions->smoothingType() == KisSmoothingOptions::STABILIZER) { m_d->stabilizedSampler.addEvent(info); } else { m_d->previousPaintInformation = info; } if(m_d->airbrushingTimer.isActive()) { m_d->airbrushingTimer.start(); } } void KisToolFreehandHelper::endPaint() { if (!m_d->hasPaintAtLeastOnce) { paintAt(m_d->previousPaintInformation); } else if (m_d->smoothingOptions->smoothingType() != KisSmoothingOptions::NO_SMOOTHING) { finishStroke(); } m_d->strokeTimeoutTimer.stop(); if(m_d->airbrushingTimer.isActive()) { m_d->airbrushingTimer.stop(); } if (m_d->smoothingOptions->smoothingType() == KisSmoothingOptions::STABILIZER) { stabilizerEnd(); } /** * There might be some timer events still pending, so * we should cancel them. Use this flag for the purpose. * Please note that we are not in MT here, so no mutex * is needed */ m_d->painterInfos.clear(); m_d->strokesFacade->endStroke(m_d->strokeId); m_d->strokeId.clear(); if(m_d->recordingAdapter) { m_d->recordingAdapter->endStroke(); } } void KisToolFreehandHelper::cancelPaint() { if (!m_d->strokeId) return; m_d->strokeTimeoutTimer.stop(); if (m_d->airbrushingTimer.isActive()) { m_d->airbrushingTimer.stop(); } if (m_d->stabilizerPollTimer.isActive()) { m_d->stabilizerPollTimer.stop(); } if (m_d->stabilizerDelayedPaintHelper.running()) { m_d->stabilizerDelayedPaintHelper.cancel(); } // see a comment in endPaint() m_d->painterInfos.clear(); m_d->strokesFacade->cancelStroke(m_d->strokeId); m_d->strokeId.clear(); if(m_d->recordingAdapter) { //FIXME: not implemented //m_d->recordingAdapter->cancelStroke(); } } int KisToolFreehandHelper::elapsedStrokeTime() const { return m_d->strokeTime.elapsed(); } void KisToolFreehandHelper::stabilizerStart(KisPaintInformation firstPaintInfo) { // FIXME: Ugly hack, this is no a "distance" in any way int sampleSize = qRound(m_d->effectiveSmoothnessDistance()); sampleSize = qMax(3, sampleSize); // Fill the deque with the current value repeated until filling the sample m_d->stabilizerDeque.clear(); for (int i = sampleSize; i > 0; i--) { m_d->stabilizerDeque.enqueue(firstPaintInfo); } // Poll and draw regularly KisConfig cfg; int stabilizerSampleSize = cfg.stabilizerSampleSize(); m_d->stabilizerPollTimer.setInterval(stabilizerSampleSize); m_d->stabilizerPollTimer.start(); int delayedPaintInterval = cfg.stabilizerDelayedPaintInterval(); if (delayedPaintInterval < stabilizerSampleSize) { m_d->stabilizerDelayedPaintHelper.start(delayedPaintInterval, firstPaintInfo); } m_d->stabilizedSampler.clear(); m_d->stabilizedSampler.addEvent(firstPaintInfo); } KisPaintInformation KisToolFreehandHelper::getStabilizedPaintInfo(const QQueue &queue, const KisPaintInformation &lastPaintInfo) { KisPaintInformation result(lastPaintInfo.pos(), lastPaintInfo.pressure(), lastPaintInfo.xTilt(), lastPaintInfo.yTilt(), lastPaintInfo.rotation(), lastPaintInfo.tangentialPressure(), lastPaintInfo.perspective(), elapsedStrokeTime(), lastPaintInfo.drawingSpeed()); if (queue.size() > 1) { QQueue::const_iterator it = queue.constBegin(); QQueue::const_iterator end = queue.constEnd(); /** * The first point is going to be overridden by lastPaintInfo, skip it. */ it++; int i = 2; if (m_d->smoothingOptions->stabilizeSensors()) { while (it != end) { qreal k = qreal(i - 1) / i; // coeff for uniform averaging result = KisPaintInformation::mixWithoutTime(k, *it, result); it++; i++; } } else{ while (it != end) { qreal k = qreal(i - 1) / i; // coeff for uniform averaging result = KisPaintInformation::mixOnlyPosition(k, *it, result); it++; i++; } } } return result; } void KisToolFreehandHelper::stabilizerPollAndPaint() { KisStabilizedEventsSampler::iterator it; KisStabilizedEventsSampler::iterator end; std::tie(it, end) = m_d->stabilizedSampler.range(); QVector delayedPaintTodoItems; for (; it != end; ++it) { KisPaintInformation sampledInfo = *it; bool canPaint = true; if (m_d->smoothingOptions->useDelayDistance()) { const qreal R = m_d->smoothingOptions->delayDistance() / m_d->resources->effectiveZoom(); QPointF diff = sampledInfo.pos() - m_d->previousPaintInformation.pos(); qreal dx = sqrt(pow2(diff.x()) + pow2(diff.y())); if (!(dx > R)) { if (m_d->resources->needsAirbrushing()) { sampledInfo.setPos(m_d->previousPaintInformation.pos()); } else { canPaint = false; } } } if (canPaint) { KisPaintInformation newInfo = getStabilizedPaintInfo(m_d->stabilizerDeque, sampledInfo); if (m_d->stabilizerDelayedPaintHelper.running()) { delayedPaintTodoItems.append(newInfo); } else { paintLine(m_d->previousPaintInformation, newInfo); } m_d->previousPaintInformation = newInfo; // Push the new entry through the queue m_d->stabilizerDeque.dequeue(); m_d->stabilizerDeque.enqueue(sampledInfo); } else if (m_d->stabilizerDeque.head().pos() != m_d->previousPaintInformation.pos()) { QQueue::iterator it = m_d->stabilizerDeque.begin(); QQueue::iterator end = m_d->stabilizerDeque.end(); while (it != end) { *it = m_d->previousPaintInformation; ++it; } } } m_d->stabilizedSampler.clear(); if (m_d->stabilizerDelayedPaintHelper.running()) { m_d->stabilizerDelayedPaintHelper.update(delayedPaintTodoItems); } else { emit requestExplicitUpdateOutline(); } } void KisToolFreehandHelper::stabilizerEnd() { // Stop the timer m_d->stabilizerPollTimer.stop(); // Finish the line if (m_d->smoothingOptions->finishStabilizedCurve()) { // Process all the existing events first stabilizerPollAndPaint(); // Draw the finish line with pending events and a time override m_d->stabilizedSampler.addFinishingEvent(m_d->stabilizerDeque.size()); stabilizerPollAndPaint(); } if (m_d->stabilizerDelayedPaintHelper.running()) { m_d->stabilizerDelayedPaintHelper.end(); } } const KisPaintOp* KisToolFreehandHelper::currentPaintOp() const { return !m_d->painterInfos.isEmpty() ? m_d->painterInfos.first()->painter->paintOp() : 0; } void KisToolFreehandHelper::finishStroke() { if (m_d->haveTangent) { m_d->haveTangent = false; QPointF newTangent = (m_d->previousPaintInformation.pos() - m_d->olderPaintInformation.pos()) / (m_d->previousPaintInformation.currentTime() - m_d->olderPaintInformation.currentTime()); paintBezierSegment(m_d->olderPaintInformation, m_d->previousPaintInformation, m_d->previousTangent, newTangent); } } void KisToolFreehandHelper::doAirbrushing() { // Check that the stroke hasn't ended. if (!m_d->painterInfos.isEmpty()) { // Add a new painting update at a point identical to the previous one, except for the time // and speed information. const KisPaintInformation &prevPaint = m_d->previousPaintInformation; KisPaintInformation nextPaint(prevPaint.pos(), prevPaint.pressure(), prevPaint.xTilt(), prevPaint.yTilt(), prevPaint.rotation(), prevPaint.tangentialPressure(), prevPaint.perspective(), elapsedStrokeTime(), 0.0); paint(nextPaint); } } int KisToolFreehandHelper::computeAirbrushTimerInterval() const { qreal realInterval = m_d->resources->airbrushingInterval() * AIRBRUSH_INTERVAL_FACTOR; return qMax(1, qFloor(realInterval)); } void KisToolFreehandHelper::paintAt(int painterInfoId, const KisPaintInformation &pi) { m_d->hasPaintAtLeastOnce = true; m_d->strokesFacade->addJob(m_d->strokeId, new FreehandStrokeStrategy::Data(m_d->resources->currentNode(), painterInfoId, pi)); if(m_d->recordingAdapter) { m_d->recordingAdapter->addPoint(pi); } } void KisToolFreehandHelper::paintLine(int painterInfoId, const KisPaintInformation &pi1, const KisPaintInformation &pi2) { m_d->hasPaintAtLeastOnce = true; m_d->strokesFacade->addJob(m_d->strokeId, new FreehandStrokeStrategy::Data(m_d->resources->currentNode(), painterInfoId, pi1, pi2)); if(m_d->recordingAdapter) { m_d->recordingAdapter->addLine(pi1, pi2); } } void KisToolFreehandHelper::paintBezierCurve(int painterInfoId, const KisPaintInformation &pi1, const QPointF &control1, const QPointF &control2, const KisPaintInformation &pi2) { #ifdef DEBUG_BEZIER_CURVES KisPaintInformation tpi1; KisPaintInformation tpi2; tpi1 = pi1; tpi2 = pi2; tpi1.setPressure(0.3); tpi2.setPressure(0.3); paintLine(tpi1, tpi2); tpi1.setPressure(0.6); tpi2.setPressure(0.3); tpi1.setPos(pi1.pos()); tpi2.setPos(control1); paintLine(tpi1, tpi2); tpi1.setPos(pi2.pos()); tpi2.setPos(control2); paintLine(tpi1, tpi2); #endif m_d->hasPaintAtLeastOnce = true; m_d->strokesFacade->addJob(m_d->strokeId, new FreehandStrokeStrategy::Data(m_d->resources->currentNode(), painterInfoId, pi1, control1, control2, pi2)); if(m_d->recordingAdapter) { m_d->recordingAdapter->addCurve(pi1, control1, control2, pi2); } } void KisToolFreehandHelper::createPainters(QVector &painterInfos, const KisDistanceInformation &startDist) { painterInfos << new PainterInfo(startDist); } void KisToolFreehandHelper::paintAt(const KisPaintInformation &pi) { paintAt(0, pi); } void KisToolFreehandHelper::paintLine(const KisPaintInformation &pi1, const KisPaintInformation &pi2) { paintLine(0, pi1, pi2); } void KisToolFreehandHelper::paintBezierCurve(const KisPaintInformation &pi1, const QPointF &control1, const QPointF &control2, const KisPaintInformation &pi2) { paintBezierCurve(0, pi1, control1, control2, pi2); } int KisToolFreehandHelper::canvasRotation() { return m_d->canvasRotation; } void KisToolFreehandHelper::setCanvasRotation(int rotation) { m_d->canvasRotation = rotation; } bool KisToolFreehandHelper::canvasMirroredH() { return m_d->canvasMirroredH; } void KisToolFreehandHelper::setCanvasHorizontalMirrorState(bool mirrored) { m_d->canvasMirroredH = mirrored; } diff --git a/plugins/extensions/qmic/QMic.cpp b/plugins/extensions/qmic/QMic.cpp index 2ba5c7d466..c4bbe48933 100644 --- a/plugins/extensions/qmic/QMic.cpp +++ b/plugins/extensions/qmic/QMic.cpp @@ -1,474 +1,468 @@ /* * Copyright (c) 2017 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "QMic.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_input_output_mapper.h" #include "kis_qmic_simple_convertor.h" #include "kis_import_qmic_processing_visitor.h" #include #include "kis_qmic_applicator.h" #include "kis_qmic_progress_manager.h" static const char ack[] = "ack"; K_PLUGIN_FACTORY_WITH_JSON(QMicFactory, "kritaqmic.json", registerPlugin();) QMic::QMic(QObject *parent, const QVariantList &) : KisViewPlugin(parent) , m_gmicApplicator(0) , m_progressManager(0) { KisPreferenceSetRegistry *preferenceSetRegistry = KisPreferenceSetRegistry::instance(); PluginSettingsFactory* settingsFactory = new PluginSettingsFactory(); preferenceSetRegistry->add("QMicPluginSettingsFactory", settingsFactory); m_qmicAction = createAction("QMic"); m_qmicAction->setActivationFlags(KisAction::ACTIVE_DEVICE); connect(m_qmicAction , SIGNAL(triggered()), this, SLOT(slotQMic())); m_againAction = createAction("QMicAgain"); m_againAction->setActivationFlags(KisAction::ACTIVE_DEVICE); m_againAction->setEnabled(false); connect(m_againAction, SIGNAL(triggered()), this, SLOT(slotQMicAgain())); m_gmicApplicator = new KisQmicApplicator(); connect(m_gmicApplicator, SIGNAL(gmicFinished(bool, int, QString)), this, SLOT(slotGmicFinished(bool, int, QString))); } QMic::~QMic() { Q_FOREACH(QSharedMemory *memorySegment, m_sharedMemorySegments) { qDebug() << "detaching" << memorySegment->key(); memorySegment->detach(); } qDeleteAll(m_sharedMemorySegments); m_sharedMemorySegments.clear(); if (m_pluginProcess) { m_pluginProcess->close(); } delete m_gmicApplicator; delete m_progressManager; delete m_localServer; } void QMic::slotQMicAgain() { slotQMic(true); } void QMic::slotQMic(bool again) { m_qmicAction->setEnabled(false); m_againAction->setEnabled(false); if (m_pluginProcess) { qDebug() << "Plugin is already started" << m_pluginProcess->state(); return; } delete m_progressManager; m_progressManager = new KisQmicProgressManager(m_view); connect(m_progressManager, SIGNAL(sigProgress()), this, SLOT(slotUpdateProgress())); // find the krita-gmic-qt plugin KisConfig cfg; QString pluginPath = cfg.readEntry("gmic_qt_plugin_path", QString::null); if (pluginPath.isEmpty() || !QFileInfo(pluginPath).exists()) { KoDialog dlg; dlg.setWindowTitle(i18nc("@title:Window", "Krita")); QWidget *w = new QWidget(&dlg); dlg.setMainWidget(w); QVBoxLayout *l = new QVBoxLayout(w); l->addWidget(new PluginSettings(w)); dlg.setButtons(KoDialog::Ok); dlg.exec(); pluginPath = cfg.readEntry("gmic_qt_plugin_path", QString::null); if (pluginPath.isEmpty() || !QFileInfo(pluginPath).exists()) { return; } } m_key = QUuid::createUuid().toString(); m_localServer = new QLocalServer(); m_localServer->listen(m_key); connect(m_localServer, SIGNAL(newConnection()), SLOT(connected())); m_pluginProcess = new QProcess(this); m_pluginProcess->setProcessChannelMode(QProcess::ForwardedChannels); connect(m_pluginProcess, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(pluginFinished(int,QProcess::ExitStatus))); connect(m_pluginProcess, SIGNAL(stateChanged(QProcess::ProcessState)), this, SLOT(pluginStateChanged(QProcess::ProcessState))); m_pluginProcess->start(pluginPath, QStringList() << m_key << (again ? QString(" reapply") : QString::null)); bool r = m_pluginProcess->waitForStarted(); while (m_pluginProcess->waitForFinished(10)) { qApp->processEvents(QEventLoop::ExcludeUserInputEvents); } qDebug() << "Plugin started" << r << m_pluginProcess->state(); } void QMic::connected() { qDebug() << "connected"; QLocalSocket *socket = m_localServer->nextPendingConnection(); if (!socket) { return; } while (socket->bytesAvailable() < static_cast(sizeof(quint32))) { if (!socket->isValid()) { // stale request return; } socket->waitForReadyRead(1000); } QDataStream ds(socket); QByteArray msg; quint32 remaining; ds >> remaining; msg.resize(remaining); int got = 0; char* uMsgBuf = msg.data(); do { got = ds.readRawData(uMsgBuf, remaining); remaining -= got; uMsgBuf += got; } while (remaining && got >= 0 && socket->waitForReadyRead(2000)); if (got < 0) { qWarning() << "Message reception failed" << socket->errorString(); delete socket; m_localServer->close(); delete m_localServer; m_localServer = 0; return; } QString message = QString::fromUtf8(msg); qDebug() << "Received" << message; // Check the message: we can get three different ones QMultiMap messageMap; Q_FOREACH(QString line, message.split('\n', QString::SkipEmptyParts)) { QList kv = line.split('=', QString::SkipEmptyParts); if (kv.size() == 2) { messageMap.insert(kv[0], kv[1]); } else { qWarning() << "line" << line << "is invalid."; } } if (!messageMap.contains("command")) { qWarning() << "Message did not contain a command"; return; } int mode = 0; if (messageMap.contains("mode")) { mode = messageMap.values("mode").first().toInt(); } QByteArray ba; if (messageMap.values("command").first() == "gmic_qt_get_image_size") { ba = QByteArray::number(m_view->image()->width()) + "," + QByteArray::number(m_view->image()->height()); } else if (messageMap.values("command").first() == "gmic_qt_get_cropped_images") { // Parse the message, create the shared memory segments, and create a new message to send back and waid for ack QRectF cropRect = m_view->image()->bounds(); - if (!messageMap.contains("croprect") || !messageMap.values("croprect").first().split(',', QString::SkipEmptyParts).size() == 4) { + if (!messageMap.contains("croprect") || messageMap.values("croprect").first().split(',', QString::SkipEmptyParts).size() != 4) { qWarning() << "gmic-qt didn't send a croprect or not a valid croprect"; } else { QStringList cr = messageMap.values("croprect").first().split(',', QString::SkipEmptyParts); cropRect.setX(cr[0].toFloat()); cropRect.setY(cr[1].toFloat()); cropRect.setWidth(cr[2].toFloat()); cropRect.setHeight(cr[3].toFloat()); } if (!prepareCroppedImages(&ba, cropRect, mode)) { qWarning() << "Failed to prepare images for gmic-qt"; } } else if (messageMap.values("command").first() == "gmic_qt_output_images") { // Parse the message. read the shared memory segments, fix up the current image and send an ack qDebug() << "gmic_qt_output_images"; QStringList layers = messageMap.values("layer"); m_outputMode = (OutputMode)mode; if (m_outputMode != IN_PLACE) { QMessageBox::warning(0, i18nc("@title:window", "Krita"), i18n("Sorry, this output mode is not implemented yet.")); m_outputMode = IN_PLACE; } slotStartApplicator(layers); } else if (messageMap.values("command").first() == "gmic_qt_detach") { Q_FOREACH(QSharedMemory *memorySegment, m_sharedMemorySegments) { qDebug() << "detaching" << memorySegment->key() << memorySegment->isAttached(); if (memorySegment->isAttached()) { if (!memorySegment->detach()) { qDebug() << "\t" << memorySegment->error() << memorySegment->errorString(); } } } qDeleteAll(m_sharedMemorySegments); m_sharedMemorySegments.clear(); } else { qWarning() << "Received unknown command" << messageMap.values("command"); } qDebug() << "Sending" << QString::fromUtf8(ba); ds.writeBytes(ba.constData(), ba.length()); // Wait for the ack - bool r; + bool r = true; r &= socket->waitForReadyRead(); // wait for ack r &= (socket->read(qstrlen(ack)) == ack); socket->waitForDisconnected(-1); } void QMic::pluginStateChanged(QProcess::ProcessState state) { qDebug() << "stateChanged" << state; } void QMic::pluginFinished(int exitCode, QProcess::ExitStatus exitStatus) { qDebug() << "pluginFinished" << exitCode << exitStatus; delete m_pluginProcess; m_pluginProcess = 0; delete m_localServer; m_localServer = 0; delete m_progressManager; m_progressManager = 0; m_qmicAction->setEnabled(true); m_againAction->setEnabled(true); } void QMic::slotUpdateProgress() { if (!m_gmicApplicator) { qWarning() << "G'Mic applicator already deleted!"; return; } qDebug() << "slotUpdateProgress" << m_gmicApplicator->getProgress(); m_progressManager->updateProgress(m_gmicApplicator->getProgress()); } void QMic::slotStartProgressReporting() { qDebug() << "slotStartProgressReporting();"; if (m_progressManager->inProgress()) { m_progressManager->finishProgress(); } m_progressManager->initProgress(); } void QMic::slotGmicFinished(bool successfully, int milliseconds, const QString &msg) { qDebug() << "slotGmicFinished();" << successfully << milliseconds << msg; if (successfully) { m_gmicApplicator->finish(); } else { m_gmicApplicator->cancel(); QMessageBox::warning(0, i18nc("@title:window", "Krita"), i18n("G'Mic failed, reason:") + msg); } } void QMic::slotStartApplicator(QStringList gmicImages) { qDebug() << "slotStartApplicator();" << gmicImages; // Create a vector of gmic images QVector *> images; Q_FOREACH(const QString &image, gmicImages) { QStringList parts = image.split(',', QString::SkipEmptyParts); Q_ASSERT(parts.size() == 4); QString key = parts[0]; QString layerName = QByteArray::fromHex(parts[1].toLatin1()); int spectrum = parts[2].toInt(); int width = parts[3].toInt(); int height = parts[4].toInt(); qDebug() << key << layerName << width << height; QSharedMemory m(key); if (!m.attach(QSharedMemory::ReadOnly)) { qWarning() << "Could not attach to shared memory area." << m.error() << m.errorString(); } if (m.isAttached()) { if (!m.lock()) { qDebug() << "Could not lock memeory segment" << m.error() << m.errorString(); } qDebug() << "Memory segment" << key << m.size() << m.constData() << m.data(); gmic_image *gimg = new gmic_image(); gimg->assign(width, height, 1, spectrum); gimg->name = layerName; gimg->_data = new float[width * height * spectrum * sizeof(float)]; qDebug() << "width" << width << "height" << height << "size" << width * height * spectrum * sizeof(float) << "shared memory size" << m.size(); memcpy(gimg->_data, m.constData(), width * height * spectrum * sizeof(float)); - - QFile f("/home/boud/imagedata.txt"); - f.open(QFile::WriteOnly); - f.write((const char*)m.constData(), m.size()); - f.close(); - qDebug() << "created gmic image" << gimg->name << gimg->_width << gimg->_height; if (!m.unlock()) { qDebug() << "Could not unlock memeory segment" << m.error() << m.errorString(); } if (!m.detach()) { qDebug() << "Could not detach from memeory segment" << m.error() << m.errorString(); } images.append(gimg); } } qDebug() << "Got" << images.size() << "gmic images"; // Start the applicator KUndo2MagicString actionName = kundo2_i18n("Gmic filter"); KisNodeSP rootNode = m_view->image()->root(); KisInputOutputMapper mapper(m_view->image(), m_view->activeNode()); KisNodeListSP layers = mapper.inputNodes(m_inputMode); m_gmicApplicator->setProperties(m_view->image(), rootNode, images, actionName, layers); slotStartProgressReporting(); m_gmicApplicator->preview(); m_gmicApplicator->finish(); } bool QMic::prepareCroppedImages(QByteArray *message, QRectF &rc, int inputMode) { m_view->image()->lock(); m_inputMode = (InputLayerMode)inputMode; qDebug() << "prepareCroppedImages()" << QString::fromUtf8(*message) << rc << inputMode; KisInputOutputMapper mapper(m_view->image(), m_view->activeNode()); KisNodeListSP nodes = mapper.inputNodes(m_inputMode); if (nodes->isEmpty()) { m_view->image()->unlock(); return false; } for (int i = 0; i < nodes->size(); ++i) { KisNodeSP node = nodes->at(i); qDebug() << "Converting node" << node->name() << node->exactBounds(); if (node->paintDevice()) { QRect cropRect = node->exactBounds(); const int ix = static_cast(std::floor(rc.x() * cropRect.width())); const int iy = static_cast(std::floor(rc.y() * cropRect.height())); const int iw = std::min(cropRect.width() - ix, static_cast(1 + std::ceil(rc.width() * cropRect.width()))); const int ih = std::min(cropRect.height() - iy, static_cast(1 + std::ceil(rc.height() * cropRect.height()))); QSharedMemory *m = new QSharedMemory(QString("key_%1").arg(QUuid::createUuid().toString())); m_sharedMemorySegments.append(m); if (!m->create(iw * ih * 4 * sizeof(float))) { //buf.size())) { qWarning() << "Could not create shared memory segment" << m->error() << m->errorString(); return false; } m->lock(); gmic_image img; img.assign(iw, ih, 1, 4); img._data = reinterpret_cast(m->data()); KisQmicSimpleConvertor::convertToGmicImageFast(node->paintDevice(), &img, QRect(ix, iy, iw, ih)); message->append(m->key().toUtf8()); m->unlock(); qDebug() << "size" << m->size(); message->append(","); message->append(node->name().toUtf8().toHex()); message->append(","); message->append(QByteArray::number(iw)); message->append(","); message->append(QByteArray::number(ih)); message->append("\n"); } } qDebug() << QString::fromUtf8(*message); m_view->image()->unlock(); return true; } #include "QMic.moc" diff --git a/plugins/extensions/qmic/kis_qmic_synchronize_image_size_command.cpp b/plugins/extensions/qmic/kis_qmic_synchronize_image_size_command.cpp index 72fde41753..bbb583747c 100644 --- a/plugins/extensions/qmic/kis_qmic_synchronize_image_size_command.cpp +++ b/plugins/extensions/qmic/kis_qmic_synchronize_image_size_command.cpp @@ -1,81 +1,82 @@ /* * Copyright (c) 2015 Lukáš Tvrdý KisQmicSynchronizeImageSizeCommand::KisQmicSynchronizeImageSizeCommand(QVector *> images, KisImageWSP image) : m_images(images) , m_image(image) , m_resizeCommand(0) { qDebug() << "KisQmicSynchronizeImageSizeCommand" << "gmic images" << m_images.size(); } KisQmicSynchronizeImageSizeCommand::~KisQmicSynchronizeImageSizeCommand() { delete m_resizeCommand; } void KisQmicSynchronizeImageSizeCommand::redo() { qDebug() << "KisQmicSynchronizeImageSizeCommand::redo"; // sync image size if (m_image) { QSize gmicBoundingLayerSize = findMaxLayerSize(m_images); QSize kritaSize = m_image->size(); qDebug() << "\tkrita image" << kritaSize << "gmic size" << gmicBoundingLayerSize; - if (kritaSize != gmicBoundingLayerSize) + if (gmicBoundingLayerSize.width() > kritaSize.width() || gmicBoundingLayerSize.height() > kritaSize.height()) { - dbgPlugins << "G'Mic resizes Krita canvas from " << kritaSize << " to " << gmicBoundingLayerSize; - m_resizeCommand = new KisImageResizeCommand(m_image, gmicBoundingLayerSize); + QSize newSize = kritaSize.expandedTo(gmicBoundingLayerSize); + dbgPlugins << "G'Mic expands Krita canvas from " << kritaSize << " to " << newSize; + m_resizeCommand = new KisImageResizeCommand(m_image, newSize); m_resizeCommand->redo(); } } } void KisQmicSynchronizeImageSizeCommand::undo() { qDebug() << "KisQmicSynchronizeImageSizeCommand::undo"; if (m_resizeCommand) { m_resizeCommand->undo(); } } QSize KisQmicSynchronizeImageSizeCommand::findMaxLayerSize(QVector *> images) { // synchronize image size int maxWidth = 0; int maxHeight = 0; for (int i = 0; i < images.size(); i++) { gmic_image *gimg = images[i]; int width = gimg->_width; maxWidth = qMax(width, maxWidth); int height = gimg->_height; maxHeight = qMax(height, maxHeight); } qDebug() << "MaxLayerSize" << maxWidth << maxHeight; return QSize(maxWidth, maxHeight); } diff --git a/plugins/impex/psd/psd_loader.cpp b/plugins/impex/psd/psd_loader.cpp index aee90777bd..e4ef769d78 100644 --- a/plugins/impex/psd/psd_loader.cpp +++ b/plugins/impex/psd/psd_loader.cpp @@ -1,371 +1,373 @@ /* * Copyright (c) 2009 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "psd_loader.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_resource_server_provider.h" #include "psd.h" #include "psd_header.h" #include "psd_colormode_block.h" #include "psd_utils.h" #include "psd_resource_section.h" #include "psd_layer_section.h" #include "psd_resource_block.h" #include "psd_image_data.h" PSDLoader::PSDLoader(KisDocument *doc) : m_image(0) , m_doc(doc) , m_stop(false) { } PSDLoader::~PSDLoader() { } KisImageBuilder_Result PSDLoader::decode(QIODevice *io) { // open the file dbgFile << "pos:" << io->pos(); PSDHeader header; if (!header.read( io)) { dbgFile << "failed reading header: " << header.error; return KisImageBuilder_RESULT_FAILURE; } dbgFile << header; dbgFile << "Read header. pos:" << io->pos(); PSDColorModeBlock colorModeBlock(header.colormode); if (!colorModeBlock.read(io)) { dbgFile << "failed reading colormode block: " << colorModeBlock.error; return KisImageBuilder_RESULT_FAILURE; } dbgFile << "Read color mode block. pos:" << io->pos(); PSDImageResourceSection resourceSection; if (!resourceSection.read(io)) { dbgFile << "failed image reading resource section: " << resourceSection.error; return KisImageBuilder_RESULT_FAILURE; } dbgFile << "Read image resource section. pos:" << io->pos(); PSDLayerMaskSection layerSection(header); if (!layerSection.read(io)) { dbgFile << "failed reading layer/mask section: " << layerSection.error; return KisImageBuilder_RESULT_FAILURE; } dbgFile << "Read layer/mask section. " << layerSection.nLayers << "layers. pos:" << io->pos(); // Done reading, except possibly for the image data block, which is only relevant if there // are no layers. // Get the right colorspace QPair colorSpaceId = psd_colormode_to_colormodelid(header.colormode, header.channelDepth); if (colorSpaceId.first.isNull()) { dbgFile << "Unsupported colorspace" << header.colormode << header.channelDepth; return KisImageBuilder_RESULT_UNSUPPORTED_COLORSPACE; } // Get the icc profile from the image resource section const KoColorProfile* profile = 0; if (resourceSection.resources.contains(PSDImageResourceSection::ICC_PROFILE)) { ICC_PROFILE_1039 *iccProfileData = dynamic_cast(resourceSection.resources[PSDImageResourceSection::ICC_PROFILE]->resource); if (iccProfileData ) { profile = KoColorSpaceRegistry::instance()->createColorProfile(colorSpaceId.first, colorSpaceId.second, iccProfileData->icc); dbgFile << "Loaded ICC profile" << profile->name(); delete resourceSection.resources.take(PSDImageResourceSection::ICC_PROFILE); } } // Create the colorspace const KoColorSpace* cs = KoColorSpaceRegistry::instance()->colorSpace(colorSpaceId.first, colorSpaceId.second, profile); if (!cs) { return KisImageBuilder_RESULT_UNSUPPORTED_COLORSPACE; } // Creating the KisImage QString name = dynamic_cast(io) ? dynamic_cast(io)->fileName() : "Imported"; m_image = new KisImage(m_doc->createUndoStore(), header.width, header.height, cs, name); Q_CHECK_PTR(m_image); m_image->lock(); // set the correct resolution if (resourceSection.resources.contains(PSDImageResourceSection::RESN_INFO)) { RESN_INFO_1005 *resInfo = dynamic_cast(resourceSection.resources[PSDImageResourceSection::RESN_INFO]->resource); if (resInfo) { - m_image->setResolution(POINT_TO_INCH(resInfo->hRes), POINT_TO_INCH(resInfo->vRes)); + // check resolution size is not zero + if (resInfo->hRes * resInfo->vRes > 0) + m_image->setResolution(POINT_TO_INCH(resInfo->hRes), POINT_TO_INCH(resInfo->vRes)); // let's skip the unit for now; we can only set that on the KisDocument, and krita doesn't use it. delete resourceSection.resources.take(PSDImageResourceSection::RESN_INFO); } } // Preserve all the annotations Q_FOREACH (PSDResourceBlock *resourceBlock, resourceSection.resources.values()) { m_image->addAnnotation(resourceBlock); } // Preserve the duotone colormode block for saving back to psd if (header.colormode == DuoTone) { KisAnnotationSP annotation = new KisAnnotation("DuotoneColormodeBlock", i18n("Duotone Colormode Block"), colorModeBlock.data); m_image->addAnnotation(annotation); } // Read the projection into our single layer. Since we only read the projection when // we have just one layer, we don't need to later on apply the alpha channel of the // first layer to the projection if the number of layers is negative/ // See http://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#50577409_16000. if (layerSection.nLayers == 0) { dbgFile << "Position" << io->pos() << "Going to read the projection into the first layer, which Photoshop calls 'Background'"; KisPaintLayerSP layer = new KisPaintLayer(m_image, i18n("Background"), OPACITY_OPAQUE_U8); PSDImageData imageData(&header); imageData.read(io, layer->paintDevice()); m_image->addNode(layer, m_image->rootLayer()); // Only one layer, the background layer, so we're done. m_image->unlock(); return KisImageBuilder_RESULT_OK; } // More than one layer, so now construct the Krita image from the info we read. QStack groupStack; groupStack.push(m_image->rootLayer()); /** * PSD has a weird "optimization": if a group layer has only one * child layer, it omits it's 'psd_bounding_divider' section. So * fi you ever see an unbalanced layers group in PSD, most * probably, it is just a single layered group. */ KisNodeSP lastAddedLayer; typedef QPair LayerStyleMapping; QVector allStylesXml; // read the channels for the various layers for(int i = 0; i < layerSection.nLayers; ++i) { PSDLayerRecord* layerRecord = layerSection.layers.at(i); dbgFile << "Going to read channels for layer" << i << layerRecord->layerName; KisLayerSP newLayer; if (layerRecord->infoBlocks.keys.contains("lsct") && layerRecord->infoBlocks.sectionDividerType != psd_other) { if (layerRecord->infoBlocks.sectionDividerType == psd_bounding_divider && !groupStack.isEmpty()) { KisGroupLayerSP groupLayer = new KisGroupLayer(m_image, "temp", OPACITY_OPAQUE_U8); m_image->addNode(groupLayer, groupStack.top()); groupStack.push(groupLayer); newLayer = groupLayer; } else if ((layerRecord->infoBlocks.sectionDividerType == psd_open_folder || layerRecord->infoBlocks.sectionDividerType == psd_closed_folder) && (groupStack.size() > 1 || (lastAddedLayer && !groupStack.isEmpty()))) { KisGroupLayerSP groupLayer; if (groupStack.size() <= 1) { groupLayer = new KisGroupLayer(m_image, "temp", OPACITY_OPAQUE_U8); m_image->addNode(groupLayer, groupStack.top()); m_image->moveNode(lastAddedLayer, groupLayer, KisNodeSP()); } else { groupLayer = groupStack.pop(); } groupLayer->setName(layerRecord->layerName); groupLayer->setVisible(layerRecord->visible); QString compositeOp = psd_blendmode_to_composite_op(layerRecord->infoBlocks.sectionDividerBlendMode); // Krita doesn't support pass-through blend // mode. Instead it is just a property of a goupr // layer, so flip it if (compositeOp == COMPOSITE_PASS_THROUGH) { compositeOp = COMPOSITE_OVER; groupLayer->setPassThroughMode(true); } groupLayer->setCompositeOpId(compositeOp); newLayer = groupLayer; } else { /** * In some files saved by PS CS6 the group layer sections seem * to be unbalanced. I don't know why it happens because the * reporter didn't provide us an example file. So here we just * check if the new layer was created, and if not, skip the * initialization of masks. * * See bug: 357559 */ warnKrita << "WARNING: Provided PSD has unbalanced group " << "layer markers. Some masks and/or layers can " << "be lost while loading this file. Please " << "report a bug to Krita developes and attach " << "this file to the bugreport\n" << " " << ppVar(layerRecord->layerName) << "\n" << " " << ppVar(layerRecord->infoBlocks.sectionDividerType) << "\n" << " " << ppVar(groupStack.size()); continue; } } else { KisPaintLayerSP layer = new KisPaintLayer(m_image, layerRecord->layerName, layerRecord->opacity); layer->setCompositeOpId(psd_blendmode_to_composite_op(layerRecord->blendModeKey)); const QDomDocument &styleXml = layerRecord->infoBlocks.layerStyleXml; if (!styleXml.isNull()) { allStylesXml << LayerStyleMapping(styleXml, layer); } if (!layerRecord->readPixelData(io, layer->paintDevice())) { dbgFile << "failed reading channels for layer: " << layerRecord->layerName << layerRecord->error; return KisImageBuilder_RESULT_FAILURE; } if (!groupStack.isEmpty()) { m_image->addNode(layer, groupStack.top()); } else { m_image->addNode(layer, m_image->root()); } layer->setVisible(layerRecord->visible); newLayer = layer; } Q_FOREACH (ChannelInfo *channelInfo, layerRecord->channelInfoRecords) { if (channelInfo->channelId < -1) { KisTransparencyMaskSP mask = new KisTransparencyMask(); mask->setName(i18n("Transparency Mask")); mask->initSelection(newLayer); if (!layerRecord->readMask(io, mask->paintDevice(), channelInfo)) { dbgFile << "failed reading masks for layer: " << layerRecord->layerName << layerRecord->error; } m_image->addNode(mask, newLayer); } } lastAddedLayer = newLayer; } const QVector &embeddedPatterns = layerSection.globalInfoSection.embeddedPatterns; KisAslLayerStyleSerializer serializer; if (!embeddedPatterns.isEmpty()) { Q_FOREACH (const QDomDocument &doc, embeddedPatterns) { serializer.registerPSDPattern(doc); } } QVector allStylesForServer; if (!allStylesXml.isEmpty()) { Q_FOREACH (const LayerStyleMapping &mapping, allStylesXml) { serializer.readFromPSDXML(mapping.first); if (serializer.styles().size() == 1) { KisPSDLayerStyleSP layerStyle = serializer.styles().first(); KisLayerSP layer = mapping.second; layerStyle->setName(layer->name()); allStylesForServer << layerStyle; layer->setLayerStyle(layerStyle->clone()); } else { warnKrita << "WARNING: Couldn't read layer style!" << ppVar(serializer.styles()); } } } if (!allStylesForServer.isEmpty()) { KisPSDLayerStyleCollectionResource *collection = new KisPSDLayerStyleCollectionResource("Embedded PSD Styles.asl"); collection->setName(i18nc("Auto-generated layer style collection name for embedded styles (collection)", "<%1> (embedded)", m_image->objectName())); KIS_SAFE_ASSERT_RECOVER_NOOP(!collection->valid()); collection->setLayerStyles(allStylesForServer); KIS_SAFE_ASSERT_RECOVER_NOOP(collection->valid()); KoResourceServer *server = KisResourceServerProvider::instance()->layerStyleCollectionServer(); server->addResource(collection, false); } m_image->unlock(); return KisImageBuilder_RESULT_OK; } KisImageBuilder_Result PSDLoader::buildImage(QIODevice *io) { return decode(io); } KisImageSP PSDLoader::image() { return m_image; } void PSDLoader::cancel() { m_stop = true; } diff --git a/plugins/paintops/chalk/kis_chalk_paintop.cpp b/plugins/paintops/chalk/kis_chalk_paintop.cpp index 926c8ff499..5c8158fcf1 100644 --- a/plugins/paintops/chalk/kis_chalk_paintop.cpp +++ b/plugins/paintops/chalk/kis_chalk_paintop.cpp @@ -1,102 +1,107 @@ /* * Copyright (c) 2008-2010 Lukáš Tvrdý * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_chalk_paintop.h" #include "kis_chalk_paintop_settings.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include KisChalkPaintOp::KisChalkPaintOp(const KisPaintOpSettingsSP settings, KisPainter * painter, KisNodeSP node, KisImageSP image) : KisPaintOp(painter) { Q_UNUSED(image); Q_UNUSED(node); m_airbrushOption.readOptionSetting(settings); m_opacityOption.readOptionSetting(settings); m_rateOption.readOptionSetting(settings); m_opacityOption.resetAllSensors(); m_rateOption.resetAllSensors(); m_properties.readOptionSetting(settings); KoColorTransformation* transfo = 0; if (m_properties.inkDepletion && m_properties.useSaturation) { transfo = painter->device()->compositionSourceColorSpace()->createColorTransformation("hsv_adjustment", QHash()); } m_chalkBrush = new ChalkBrush(&m_properties, transfo); } KisChalkPaintOp::~KisChalkPaintOp() { delete m_chalkBrush; } KisSpacingInformation KisChalkPaintOp::paintAt(const KisPaintInformation& info) { if (!painter()) return KisSpacingInformation(1.0); if (!m_dab) { m_dab = source()->createCompositionSourceDevice(); } else { m_dab->clear(); } qreal x1, y1; x1 = info.pos().x(); y1 = info.pos().y(); const qreal additionalScale = KisLodTransform::lodToScale(painter()->device()); quint8 origOpacity = m_opacityOption.apply(painter(), info); m_chalkBrush->paint(m_dab, x1, y1, painter()->paintColor(), additionalScale); QRect rc = m_dab->extent(); painter()->bitBlt(rc.x(), rc.y(), m_dab, rc.x(), rc.y(), rc.width(), rc.height()); painter()->renderMirrorMask(rc, m_dab); painter()->setOpacity(origOpacity); return updateSpacingImpl(info); } KisSpacingInformation KisChalkPaintOp::updateSpacingImpl(const KisPaintInformation &info) const { return KisPaintOpPluginUtils::effectiveSpacing(1.0, 1.0, true, 0.0, false, 1.0, false, 1.0, 1.0, - &m_airbrushOption, nullptr, &m_rateOption, info); + &m_airbrushOption, nullptr, info); +} + +KisTimingInformation KisChalkPaintOp::updateTimingImpl(const KisPaintInformation &info) const +{ + return KisPaintOpPluginUtils::effectiveTiming(&m_airbrushOption, &m_rateOption, info); } diff --git a/plugins/paintops/chalk/kis_chalk_paintop.h b/plugins/paintops/chalk/kis_chalk_paintop.h index bdbcef1636..2cec5ce181 100644 --- a/plugins/paintops/chalk/kis_chalk_paintop.h +++ b/plugins/paintops/chalk/kis_chalk_paintop.h @@ -1,56 +1,58 @@ /* * Copyright (c) 2008 Boudewijn Rempt * Copyright (c) 2008, 2009 Lukáš Tvrdý * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_CHALK_PAINTOP_H_ #define KIS_CHALK_PAINTOP_H_ #include #include #include "chalk_brush.h" #include "kis_chalk_paintop_settings.h" #include "kis_airbrush_option.h" #include "kis_pressure_rate_option.h" class KisPainter; class KisChalkPaintOp : public KisPaintOp { public: KisChalkPaintOp(const KisPaintOpSettingsSP settings, KisPainter *painter, KisNodeSP node, KisImageSP image); ~KisChalkPaintOp() override; protected: KisSpacingInformation paintAt(const KisPaintInformation& info) override; KisSpacingInformation updateSpacingImpl(const KisPaintInformation &info) const override; + KisTimingInformation updateTimingImpl(const KisPaintInformation &info) const override; + private: KisPaintDeviceSP m_dab; ChalkBrush * m_chalkBrush; KisAirbrushOption m_airbrushOption; KisPressureOpacityOption m_opacityOption; KisPressureRateOption m_rateOption; ChalkProperties m_properties; }; #endif // KIS_CHALK_PAINTOP_H_ diff --git a/plugins/paintops/colorsmudge/kis_colorsmudgeop.cpp b/plugins/paintops/colorsmudge/kis_colorsmudgeop.cpp index bf4781d80e..dc316b4629 100644 --- a/plugins/paintops/colorsmudge/kis_colorsmudgeop.cpp +++ b/plugins/paintops/colorsmudge/kis_colorsmudgeop.cpp @@ -1,294 +1,295 @@ /* * Copyright (C) 2011 Silvio Heinrich * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_colorsmudgeop.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include +#include KisColorSmudgeOp::KisColorSmudgeOp(const KisPaintOpSettingsSP settings, KisPainter* painter, KisNodeSP node, KisImageSP image) : KisBrushBasedPaintOp(settings, painter) , m_firstRun(true) , m_image(image) , m_tempDev(painter->device()->createCompositionSourceDevice()) , m_backgroundPainter(new KisPainter(m_tempDev)) , m_smudgePainter(new KisPainter(m_tempDev)) , m_colorRatePainter(new KisPainter(m_tempDev)) , m_smudgeRateOption() , m_colorRateOption("ColorRate", KisPaintOpOption::GENERAL, false) , m_smudgeRadiusOption() { Q_UNUSED(node); Q_ASSERT(settings); Q_ASSERT(painter); m_sizeOption.readOptionSetting(settings); m_opacityOption.readOptionSetting(settings); m_spacingOption.readOptionSetting(settings); m_smudgeRateOption.readOptionSetting(settings); m_colorRateOption.readOptionSetting(settings); m_smudgeRadiusOption.readOptionSetting(settings); m_overlayModeOption.readOptionSetting(settings); m_rotationOption.readOptionSetting(settings); m_scatterOption.readOptionSetting(settings); m_gradientOption.readOptionSetting(settings); m_sizeOption.resetAllSensors(); m_opacityOption.resetAllSensors(); m_spacingOption.resetAllSensors(); m_smudgeRateOption.resetAllSensors(); m_colorRateOption.resetAllSensors(); m_smudgeRadiusOption.resetAllSensors(); m_rotationOption.resetAllSensors(); m_scatterOption.resetAllSensors(); m_gradientOption.resetAllSensors(); m_gradient = painter->gradient(); m_backgroundPainter->setCompositeOp(COMPOSITE_COPY); // Smudge Painter works in default COMPOSITE_OVER mode m_colorRatePainter->setCompositeOp(painter->compositeOp()->id()); m_rotationOption.applyFanCornersInfo(this); } KisColorSmudgeOp::~KisColorSmudgeOp() { delete m_backgroundPainter; delete m_colorRatePainter; delete m_smudgePainter; } void KisColorSmudgeOp::updateMask(const KisPaintInformation& info, double scale, double rotation, const QPointF &cursorPoint) { static const KoColorSpace *cs = KoColorSpaceRegistry::instance()->alpha8(); static KoColor color(Qt::black, cs); m_maskDab = m_dabCache->fetchDab(cs, color, cursorPoint, KisDabShape(scale, 1.0, rotation), info, 1.0, &m_dstDabRect); // sanity check KIS_ASSERT_RECOVER_NOOP(m_dstDabRect.size() == m_maskDab->bounds().size()); } inline void KisColorSmudgeOp::getTopLeftAligned(const QPointF &pos, const QPointF &hotSpot, qint32 *x, qint32 *y) { QPointF topLeft = pos - hotSpot; qreal xFraction, yFraction; // will not be used splitCoordinate(topLeft.x(), x, &xFraction); splitCoordinate(topLeft.y(), y, &yFraction); } KisSpacingInformation KisColorSmudgeOp::paintAt(const KisPaintInformation& info) { KisBrushSP brush = m_brush; // Simple error catching if (!painter()->device() || !brush || !brush->canPaintFor(info)) { return KisSpacingInformation(1.0); } if (m_smudgeRateOption.getMode() == KisSmudgeOption::SMEARING_MODE) { /** * Disable handling of the subpixel precision. In the smudge op we * should read from the aligned areas of the image, so having * additional internal offsets, created by the subpixel precision, * will worsen the quality (at least because * QRectF(m_dstDabRect).center() will not point to the real center * of the brush anymore). * Of course, this only really matters with smearing_mode (bug:327235), * and you only notice the lack of subpixel precision in the dulling methods. */ m_dabCache->disableSubpixelPrecision(); } #if 0 //if precision KoColor colorSpaceChanger = painter()->paintColor(); const KoColorSpace* preciseColorSpace = colorSpaceChanger.colorSpace(); if (colorSpaceChanger.colorSpace()->colorDepthId().id() == "U8") { preciseColorSpace = KoColorSpaceRegistry::instance()->colorSpace(colorSpaceChanger.colorSpace()->colorModelId().id(), "U16", colorSpaceChanger.profile() ); colorSpaceChanger.convertTo(preciseColorSpace); } painter()->setPaintColor(colorSpaceChanger); #endif // get the scaling factor calculated by the size option qreal scale = m_sizeOption.apply(info); scale *= KisLodTransform::lodToScale(painter()->device()); qreal rotation = m_rotationOption.apply(info); if (checkSizeTooSmall(scale)) return KisSpacingInformation(); KisDabShape shape(scale, 1.0, rotation); QPointF scatteredPos = m_scatterOption.apply(info, brush->maskWidth(shape, 0, 0, info), brush->maskHeight(shape, 0, 0, info)); QPointF hotSpot = brush->hotSpot(shape, info); /** * Update the brush mask. * * Upon leaving the function: * o m_maskDab stores the new mask * o m_maskBounds stores the extents of the mask paint device * o m_dstDabRect stores the destination rect where the mask is going * to be written to */ updateMask(info, scale, rotation, scatteredPos); QPointF newCenterPos = QRectF(m_dstDabRect).center(); /** * Save the center of the current dab to know where to read the * data during the next pass. We do not save scatteredPos here, * because it may differ slightly from the real center of the * brush (due to rounding effects), which will result in a * really weird quality. */ QRect srcDabRect = m_dstDabRect.translated((m_lastPaintPos - newCenterPos).toPoint()); m_lastPaintPos = newCenterPos; KisSpacingInformation spacingInfo = effectiveSpacing(scale, rotation, m_spacingOption, info); if (m_firstRun) { m_firstRun = false; return spacingInfo; } // save the old opacity value and composite mode quint8 oldOpacity = painter()->opacity(); QString oldCompositeOpId = painter()->compositeOp()->id(); qreal fpOpacity = (qreal(oldOpacity) / 255.0) * m_opacityOption.getOpacityf(info); if (m_image && m_overlayModeOption.isChecked()) { m_image->blockUpdates(); m_backgroundPainter->bitBlt(QPoint(), m_image->projection(), srcDabRect); m_image->unblockUpdates(); } else { // IMPORTANT: clear the temporary painting device to color black with zero opacity: // it will only clear the extents of the brush. m_tempDev->clear(QRect(QPoint(), m_dstDabRect.size())); } if (m_smudgeRateOption.getMode() == KisSmudgeOption::SMEARING_MODE) { m_smudgePainter->bitBlt(QPoint(), painter()->device(), srcDabRect); } else { QPoint pt = (srcDabRect.topLeft() + hotSpot).toPoint(); if (m_smudgeRadiusOption.isChecked()) { qreal effectiveSize = 0.5 * (m_dstDabRect.width() + m_dstDabRect.height()); m_smudgeRadiusOption.apply(*m_smudgePainter, info, effectiveSize, pt.x(), pt.y(), painter()->device()); KoColor color2 = m_smudgePainter->paintColor(); m_smudgePainter->fill(0, 0, m_dstDabRect.width(), m_dstDabRect.height(), color2); } else { KoColor color = painter()->paintColor(); // get the pixel on the canvas that lies beneath the hot spot // of the dab and fill the temporary paint device with that color KisCrossDeviceColorPickerInt colorPicker(painter()->device(), color); colorPicker.pickColor(pt.x(), pt.y(), color.data()); m_smudgePainter->fill(0, 0, m_dstDabRect.width(), m_dstDabRect.height(), color); } } // if the user selected the color smudge option, // we will mix some color into the temporary painting device (m_tempDev) if (m_colorRateOption.isChecked()) { // this will apply the opacity (selected by the user) to copyPainter // (but fit the rate inbetween the range 0.0 to (1.0-SmudgeRate)) qreal maxColorRate = qMax(1.0 - m_smudgeRateOption.getRate(), 0.2); m_colorRateOption.apply(*m_colorRatePainter, info, 0.0, maxColorRate, fpOpacity); // paint a rectangle with the current color (foreground color) // or a gradient color (if enabled) // into the temporary painting device and use the user selected // composite mode KoColor color = painter()->paintColor(); m_gradientOption.apply(color, m_gradient, info); m_colorRatePainter->fill(0, 0, m_dstDabRect.width(), m_dstDabRect.height(), color); } // if color is disabled (only smudge) and "overlay mode" is enabled // then first blit the region under the brush from the image projection // to the painting device to prevent a rapid build up of alpha value // if the color to be smudged is semi transparent. if (m_image && m_overlayModeOption.isChecked() && !m_colorRateOption.isChecked()) { painter()->setCompositeOp(COMPOSITE_COPY); painter()->setOpacity(OPACITY_OPAQUE_U8); m_image->blockUpdates(); painter()->bitBlt(m_dstDabRect.topLeft(), m_image->projection(), m_dstDabRect); m_image->unblockUpdates(); } // set opacity calculated by the rate option m_smudgeRateOption.apply(*painter(), info, 0.0, 1.0, fpOpacity); // then blit the temporary painting device on the canvas at the current brush position // the alpha mask (maskDab) will be used here to only blit the pixels that are in the area (shape) of the brush painter()->setCompositeOp(COMPOSITE_COPY); painter()->bitBltWithFixedSelection(m_dstDabRect.x(), m_dstDabRect.y(), m_tempDev, m_maskDab, m_dstDabRect.width(), m_dstDabRect.height()); painter()->renderMirrorMaskSafe(m_dstDabRect, m_tempDev, 0, 0, m_maskDab, !m_dabCache->needSeparateOriginal()); // restore orginal opacy and composite mode values painter()->setOpacity(oldOpacity); painter()->setCompositeOp(oldCompositeOpId); return spacingInfo; } KisSpacingInformation KisColorSmudgeOp::updateSpacingImpl(const KisPaintInformation &info) const { const qreal scale = m_sizeOption.apply(info) * KisLodTransform::lodToScale(painter()->device()); const qreal rotation = m_rotationOption.apply(info); return effectiveSpacing(scale, rotation, m_spacingOption, info); } diff --git a/plugins/paintops/curvebrush/kis_curve_paintop.cpp b/plugins/paintops/curvebrush/kis_curve_paintop.cpp index 27d2cf02ff..295ee55d0b 100644 --- a/plugins/paintops/curvebrush/kis_curve_paintop.cpp +++ b/plugins/paintops/curvebrush/kis_curve_paintop.cpp @@ -1,132 +1,133 @@ /* * Copyright (c) 2008-2011 Lukáš Tvrdý * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_curve_paintop.h" #include #include #include #include #include "kis_global.h" #include "kis_paint_device.h" #include "kis_painter.h" #include "kis_types.h" +#include "kis_spacing_information.h" #include KisCurvePaintOp::KisCurvePaintOp(const KisPaintOpSettingsSP settings, KisPainter * painter, KisNodeSP node, KisImageSP image) : KisPaintOp(painter) , m_painter(0) { Q_ASSERT(settings); Q_UNUSED(image); Q_UNUSED(node); m_curveProperties.readOptionSetting(settings); m_opacityOption.readOptionSetting(settings); m_lineWidthOption.readOptionSetting(settings); m_curvesOpacityOption.readOptionSetting(settings); } KisCurvePaintOp::~KisCurvePaintOp() { delete m_painter; } KisSpacingInformation KisCurvePaintOp::paintAt(const KisPaintInformation& info) { return updateSpacingImpl(info); } KisSpacingInformation KisCurvePaintOp::updateSpacingImpl(const KisPaintInformation &info) const { Q_UNUSED(info); return KisSpacingInformation(1.0); } void KisCurvePaintOp::paintLine(const KisPaintInformation &pi1, const KisPaintInformation &pi2, KisDistanceInformation *currentDistance) { Q_UNUSED(currentDistance); if (!painter()) return; if (!m_dab) { m_dab = source()->createCompositionSourceDevice(); } else { m_dab->clear(); } paintLine(m_dab, pi1, pi2); QRect rc = m_dab->extent(); quint8 origOpacity = m_opacityOption.apply(painter(), pi2); painter()->bitBlt(rc.topLeft(), m_dab, rc); painter()->renderMirrorMask(rc, m_dab); painter()->setOpacity(origOpacity); } void KisCurvePaintOp::paintLine(KisPaintDeviceSP dab, const KisPaintInformation &pi1, const KisPaintInformation &pi2) { if (!m_painter) { m_painter = new KisPainter(dab); m_painter->setPaintColor(painter()->paintColor()); } int maxPoints = m_curveProperties.curve_stroke_history_size; m_points.append(pi2.pos()); while (m_points.length() > maxPoints) { m_points.removeFirst(); } const qreal additionalScale = KisLodTransform::lodToScale(painter()->device()); const qreal lineWidth = additionalScale * m_lineWidthOption.apply(pi2, m_curveProperties.curve_line_width); QPen pen(QBrush(Qt::white), lineWidth); QPainterPath path; if (m_curveProperties.curve_paint_connection_line) { path.moveTo(pi1.pos()); path.lineTo(pi2.pos()); m_painter->drawPainterPath(path, pen); path = QPainterPath(); } if (m_points.length() >= maxPoints) { // alpha * 0.2; path.moveTo(m_points.first()); if (m_curveProperties.curve_smoothing) { path.quadTo(m_points.at(maxPoints / 2), m_points.last()); } else { // control point is at 1/3 of the history, 2/3 of the history and endpoint at 3/3 int step = maxPoints / 3; path.cubicTo(m_points.at(step), m_points.at(step + step), m_points.last()); } qreal curveOpacity = m_curvesOpacityOption.apply(pi2, m_curveProperties.curve_curves_opacity); m_painter->setOpacity(qRound(255.0 * curveOpacity)); m_painter->drawPainterPath(path, pen); m_painter->setOpacity(255); // full } } diff --git a/plugins/paintops/defaultpaintops/brush/kis_brushop.cpp b/plugins/paintops/defaultpaintops/brush/kis_brushop.cpp index 3bff3a66e7..dbf10dac83 100644 --- a/plugins/paintops/defaultpaintops/brush/kis_brushop.cpp +++ b/plugins/paintops/defaultpaintops/brush/kis_brushop.cpp @@ -1,205 +1,209 @@ /* * Copyright (c) 2002 Patrick Julien * Copyright (c) 2004-2008 Boudewijn Rempt * Copyright (c) 2004 Clarence Dang * Copyright (c) 2004 Adrian Page * Copyright (c) 2004 Cyrille Berger * Copyright (c) 2010 Lukáš Tvrdý * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_brushop.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include +#include KisBrushOp::KisBrushOp(const KisPaintOpSettingsSP settings, KisPainter *painter, KisNodeSP node, KisImageSP image) : KisBrushBasedPaintOp(settings, painter) , m_opacityOption(node) , m_hsvTransformation(0) { Q_UNUSED(image); Q_ASSERT(settings); KisColorSourceOption colorSourceOption; colorSourceOption.readOptionSetting(settings); m_colorSource = colorSourceOption.createColorSource(painter); m_hsvOptions.append(KisPressureHSVOption::createHueOption()); m_hsvOptions.append(KisPressureHSVOption::createSaturationOption()); m_hsvOptions.append(KisPressureHSVOption::createValueOption()); Q_FOREACH (KisPressureHSVOption * option, m_hsvOptions) { option->readOptionSetting(settings); option->resetAllSensors(); if (option->isChecked() && !m_hsvTransformation) { m_hsvTransformation = painter->backgroundColor().colorSpace()->createColorTransformation("hsv_adjustment", QHash()); } } m_airbrushOption.readOptionSetting(settings); m_opacityOption.readOptionSetting(settings); m_flowOption.readOptionSetting(settings); m_sizeOption.readOptionSetting(settings); m_ratioOption.readOptionSetting(settings); m_spacingOption.readOptionSetting(settings); m_rateOption.readOptionSetting(settings); m_softnessOption.readOptionSetting(settings); m_sharpnessOption.readOptionSetting(settings); m_darkenOption.readOptionSetting(settings); m_rotationOption.readOptionSetting(settings); m_mixOption.readOptionSetting(settings); m_scatterOption.readOptionSetting(settings); m_opacityOption.resetAllSensors(); m_flowOption.resetAllSensors(); m_sizeOption.resetAllSensors(); m_ratioOption.resetAllSensors(); m_rateOption.resetAllSensors(); m_softnessOption.resetAllSensors(); m_sharpnessOption.resetAllSensors(); m_darkenOption.resetAllSensors(); m_rotationOption.resetAllSensors(); m_scatterOption.resetAllSensors(); m_dabCache->setSharpnessPostprocessing(&m_sharpnessOption); m_rotationOption.applyFanCornersInfo(this); } KisBrushOp::~KisBrushOp() { qDeleteAll(m_hsvOptions); delete m_colorSource; delete m_hsvTransformation; } KisSpacingInformation KisBrushOp::paintAt(const KisPaintInformation& info) { if (!painter()->device()) return KisSpacingInformation(1.0); KisBrushSP brush = m_brush; Q_ASSERT(brush); if (!brush) return KisSpacingInformation(1.0); if (!brush->canPaintFor(info)) return KisSpacingInformation(1.0); qreal scale = m_sizeOption.apply(info); scale *= KisLodTransform::lodToScale(painter()->device()); if (checkSizeTooSmall(scale)) return KisSpacingInformation(); qreal rotation = m_rotationOption.apply(info); qreal ratio = m_ratioOption.apply(info); KisPaintDeviceSP device = painter()->device(); KisDabShape shape(scale, ratio, rotation); QPointF cursorPos = m_scatterOption.apply(info, brush->maskWidth(shape, 0, 0, info), brush->maskHeight(shape, 0, 0, info)); quint8 origOpacity = painter()->opacity(); m_opacityOption.setFlow(m_flowOption.apply(info)); m_opacityOption.apply(painter(), info); m_colorSource->selectColor(m_mixOption.apply(info), info); m_darkenOption.apply(m_colorSource, info); if (m_hsvTransformation) { Q_FOREACH (KisPressureHSVOption * option, m_hsvOptions) { option->apply(m_hsvTransformation, info); } m_colorSource->applyColorTransformation(m_hsvTransformation); } QRect dabRect; KisFixedPaintDeviceSP dab = m_dabCache->fetchDab(device->compositionSourceColorSpace(), m_colorSource, cursorPos, shape, info, m_softnessOption.apply(info), &dabRect); // sanity check for the size calculation code if (dab->bounds().size() != dabRect.size()) { warnKrita << "KisBrushOp: dab bounds is not dab rect. See bug 327156" << dab->bounds().size() << dabRect.size(); } painter()->bltFixed(dabRect.topLeft(), dab, dab->bounds()); painter()->renderMirrorMaskSafe(dabRect, dab, !m_dabCache->needSeparateOriginal()); painter()->setOpacity(origOpacity); - return effectiveSpacing(scale, rotation, &m_airbrushOption, &m_spacingOption, &m_rateOption, - info); + return effectiveSpacing(scale, rotation, &m_airbrushOption, &m_spacingOption, info); } KisSpacingInformation KisBrushOp::updateSpacingImpl(const KisPaintInformation &info) const { const qreal scale = m_sizeOption.apply(info) * KisLodTransform::lodToScale(painter()->device()); qreal rotation = m_rotationOption.apply(info); - return effectiveSpacing(scale, rotation, &m_airbrushOption, &m_spacingOption, &m_rateOption, - info); + return effectiveSpacing(scale, rotation, &m_airbrushOption, &m_spacingOption, info); +} + +KisTimingInformation KisBrushOp::updateTimingImpl(const KisPaintInformation &info) const +{ + return KisPaintOpPluginUtils::effectiveTiming(&m_airbrushOption, &m_rateOption, info); } void KisBrushOp::paintLine(const KisPaintInformation& pi1, const KisPaintInformation& pi2, KisDistanceInformation *currentDistance) { if (m_sharpnessOption.isChecked() && m_brush && (m_brush->width() == 1) && (m_brush->height() == 1)) { if (!m_lineCacheDevice) { m_lineCacheDevice = source()->createCompositionSourceDevice(); } else { m_lineCacheDevice->clear(); } KisPainter p(m_lineCacheDevice); p.setPaintColor(painter()->paintColor()); p.drawDDALine(pi1.pos(), pi2.pos()); QRect rc = m_lineCacheDevice->extent(); painter()->bitBlt(rc.x(), rc.y(), m_lineCacheDevice, rc.x(), rc.y(), rc.width(), rc.height()); //fixes Bug 338011 painter()->renderMirrorMask(rc, m_lineCacheDevice); } else { KisPaintOp::paintLine(pi1, pi2, currentDistance); } } diff --git a/plugins/paintops/defaultpaintops/brush/kis_brushop.h b/plugins/paintops/defaultpaintops/brush/kis_brushop.h index 0837f1b7b8..51e7b52fe0 100644 --- a/plugins/paintops/defaultpaintops/brush/kis_brushop.h +++ b/plugins/paintops/defaultpaintops/brush/kis_brushop.h @@ -1,85 +1,87 @@ /* * Copyright (c) 2002 Patrick Julien * Copyright (c) 2004-2008 Boudewijn Rempt * Copyright (c) 2004 Clarence Dang * Copyright (c) 2004 Adrian Page * Copyright (c) 2004 Cyrille Berger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_BRUSHOP_H_ #define KIS_BRUSHOP_H_ #include "kis_brush_based_paintop.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include class KisPainter; class KisColorSource; class KisBrushOp : public KisBrushBasedPaintOp { public: KisBrushOp(const KisPaintOpSettingsSP settings, KisPainter * painter, KisNodeSP node, KisImageSP image); ~KisBrushOp() override; void paintLine(const KisPaintInformation &pi1, const KisPaintInformation &pi2, KisDistanceInformation *currentDistance) override; protected: KisSpacingInformation paintAt(const KisPaintInformation& info) override; KisSpacingInformation updateSpacingImpl(const KisPaintInformation &info) const override; + KisTimingInformation updateTimingImpl(const KisPaintInformation &info) const override; + private: KisColorSource *m_colorSource; KisAirbrushOption m_airbrushOption; KisPressureSizeOption m_sizeOption; KisPressureRatioOption m_ratioOption; KisPressureSpacingOption m_spacingOption; KisPressureRateOption m_rateOption; KisPressureFlowOption m_flowOption; KisFlowOpacityOption m_opacityOption; KisPressureSoftnessOption m_softnessOption; KisPressureSharpnessOption m_sharpnessOption; KisPressureDarkenOption m_darkenOption; KisPressureRotationOption m_rotationOption; KisPressureMixOption m_mixOption; KisPressureScatterOption m_scatterOption; QList m_hsvOptions; KoColorTransformation *m_hsvTransformation; KisPaintDeviceSP m_lineCacheDevice; KisPaintDeviceSP m_colorSourceDevice; }; #endif // KIS_BRUSHOP_H_ diff --git a/plugins/paintops/defaultpaintops/duplicate/kis_duplicateop.cpp b/plugins/paintops/defaultpaintops/duplicate/kis_duplicateop.cpp index 13affff993..2767b3634c 100644 --- a/plugins/paintops/defaultpaintops/duplicate/kis_duplicateop.cpp +++ b/plugins/paintops/defaultpaintops/duplicate/kis_duplicateop.cpp @@ -1,294 +1,295 @@ /* * Copyright (c) 2002 Patrick Julien * Copyright (c) 2004-2008 Boudewijn Rempt * Copyright (c) 2004 Clarence Dang * Copyright (c) 2004 Adrian Page * Copyright (c) 2004,2010 Cyrille Berger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_duplicateop.h" #include "kis_duplicateop_p.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include +#include #include "kis_duplicateop_settings.h" #include "kis_duplicateop_settings_widget.h" #include "kis_duplicateop_option.h" KisDuplicateOp::KisDuplicateOp(const KisPaintOpSettingsSP settings, KisPainter *painter, KisNodeSP node, KisImageSP image) : KisBrushBasedPaintOp(settings, painter) , m_image(image) , m_node(node) , m_settings(static_cast(const_cast(settings.data()))) { Q_ASSERT(settings); Q_ASSERT(painter); m_sizeOption.readOptionSetting(settings); m_rotationOption.readOptionSetting(settings); m_sizeOption.resetAllSensors(); m_rotationOption.resetAllSensors(); m_healing = settings->getBool(DUPLICATE_HEALING); m_perspectiveCorrection = settings->getBool(DUPLICATE_CORRECT_PERSPECTIVE); m_moveSourcePoint = settings->getBool(DUPLICATE_MOVE_SOURCE_POINT); m_cloneFromProjection = settings->getBool(DUPLICATE_CLONE_FROM_PROJECTION); m_srcdev = source()->createCompositionSourceDevice(); } KisDuplicateOp::~KisDuplicateOp() { } #define CLAMP(x,l,u) ((x)<(l)?(l):((x)>(u)?(u):(x))) KisSpacingInformation KisDuplicateOp::paintAt(const KisPaintInformation& info) { if (!painter()->device()) return KisSpacingInformation(1.0); KisBrushSP brush = m_brush; if (!brush) return KisSpacingInformation(1.0); if (!brush->canPaintFor(info)) return KisSpacingInformation(1.0); if (!m_duplicateStartIsSet) { m_duplicateStartIsSet = true; m_duplicateStart = info.pos(); } KisPaintDeviceSP realSourceDevice; if (m_cloneFromProjection && m_image) { realSourceDevice = m_image->projection(); } else { KisNodeSP externalSourceNode = m_settings->sourceNode(); /** * The saved layer might have been deleted by then, so check if it * still belongs to a graph */ if (!externalSourceNode || !externalSourceNode->graphListener()) { externalSourceNode = m_node; } realSourceDevice = externalSourceNode->projection(); } qreal rotation = m_rotationOption.apply(info); qreal scale = m_sizeOption.apply(info); if (checkSizeTooSmall(scale)) return KisSpacingInformation(); KisDabShape shape(scale, 1.0, rotation); static const KoColorSpace *cs = KoColorSpaceRegistry::instance()->alpha8(); static KoColor color(Qt::black, cs); QRect dstRect; KisFixedPaintDeviceSP dab = m_dabCache->fetchDab(cs, color, info.pos(), shape, info, 1.0, &dstRect); if (dstRect.isEmpty()) return KisSpacingInformation(1.0); QPoint srcPoint; if (m_moveSourcePoint) { srcPoint = (dstRect.topLeft() - m_settings->offset()).toPoint(); } else { QPointF hotSpot = brush->hotSpot(shape, info); srcPoint = (m_settings->position() - hotSpot).toPoint(); } qint32 sw = dstRect.width(); qint32 sh = dstRect.height(); // Perspective correction ? // if (m_perspectiveCorrection && m_image && m_image->perspectiveGrid()->countSubGrids() == 1) { // Matrix3qreal startM = Matrix3qreal::Identity(); // Matrix3qreal endM = Matrix3qreal::Identity(); // // First look for the grid corresponding to the start point // KisSubPerspectiveGrid* subGridStart = *m_image->perspectiveGrid()->begin(); // QRect r = QRect(0, 0, m_image->width(), m_image->height()); // if (subGridStart) { // startM = KisPerspectiveMath::computeMatrixTransfoFromPerspective(r, *subGridStart->topLeft(), *subGridStart->topRight(), *subGridStart->bottomLeft(), *subGridStart->bottomRight()); // } // // Second look for the grid corresponding to the end point // KisSubPerspectiveGrid* subGridEnd = *m_image->perspectiveGrid()->begin(); // if (subGridEnd) { // endM = KisPerspectiveMath::computeMatrixTransfoToPerspective(*subGridEnd->topLeft(), *subGridEnd->topRight(), *subGridEnd->bottomLeft(), *subGridEnd->bottomRight(), r); // } // // Compute the translation in the perspective transformation space: // QPointF positionStartPaintingT = KisPerspectiveMath::matProd(endM, QPointF(m_duplicateStart)); // QPointF duplicateStartPositionT = KisPerspectiveMath::matProd(endM, QPointF(m_duplicateStart) - QPointF(m_settings->offset())); // QPointF translat = duplicateStartPositionT - positionStartPaintingT; // KisSequentialIterator dstIt(m_srcdev, QRect(0, 0, sw, sh)); // KisRandomSubAccessorSP srcAcc = realSourceDevice->createRandomSubAccessor(); // //Action // do { // QPointF p = KisPerspectiveMath::matProd(startM, KisPerspectiveMath::matProd(endM, QPointF(dstIt.x() + dstRect.x(), dstIt.y() + dstRect.y())) + translat); // srcAcc->moveTo(p); // srcAcc->sampledOldRawData(dstIt.rawData()); // } while (dstIt.nextPixel()); // } // else { KisPainter copyPainter(m_srcdev); copyPainter.setCompositeOp(COMPOSITE_COPY); copyPainter.bitBltOldData(0, 0, realSourceDevice, srcPoint.x(), srcPoint.y(), sw, sh); copyPainter.end(); } // heal ? if (m_healing) { QRect healRect(dstRect); const bool smallWidth = healRect.width() < 3; const bool smallHeight = healRect.height() < 3; if (smallWidth || smallHeight) { healRect.adjust(-smallWidth, -smallHeight, smallWidth, smallHeight); } const int healSW = healRect.width(); const int healSH = healRect.height(); quint16 srcData[4]; quint16 tmpData[4]; QScopedArrayPointer matrix(new qreal[ 3 * healSW * healSH ]); // First divide const KoColorSpace* srcCs = realSourceDevice->colorSpace(); const KoColorSpace* tmpCs = m_srcdev->colorSpace(); KisHLineConstIteratorSP srcIt = realSourceDevice->createHLineConstIteratorNG(healRect.x(), healRect.y() , healSW); KisHLineIteratorSP tmpIt = m_srcdev->createHLineIteratorNG(0, 0, healSW); qreal* matrixIt = matrix.data(); for (int j = 0; j < healSH; j++) { for (int i = 0; i < healSW; i++) { srcCs->toLabA16(srcIt->oldRawData(), (quint8*)srcData, 1); tmpCs->toLabA16(tmpIt->rawData(), (quint8*)tmpData, 1); // Division for (int k = 0; k < 3; k++) { matrixIt[k] = srcData[k] / (qreal)qMax((int)tmpData [k], 1); } srcIt->nextPixel(); tmpIt->nextPixel(); matrixIt += 3; } srcIt->nextRow(); tmpIt->nextRow(); } // Minimize energy { int iter = 0; qreal err; QScopedArrayPointer solution(new qreal[ 3 * healSW * healSH ]); do { err = DuplicateOpUtils::minimizeEnergy(matrix.data(), solution.data(), healSW, healSH); solution.swap(matrix); iter++; } while (err > 0.00001 && iter < 100); } // Finally multiply KisHLineIteratorSP tmpIt2 = m_srcdev->createHLineIteratorNG(0, 0, healSW); matrixIt = &matrix[0]; for (int j = 0; j < healSH; j++) { for (int i = 0; i < healSW; i++) { tmpCs->toLabA16(tmpIt2->rawData(), (quint8*)tmpData, 1); // Multiplication for (int k = 0; k < 3; k++) { tmpData[k] = (int)CLAMP(matrixIt[k] * qMax((int) tmpData[k], 1), 0, 65535); } tmpCs->fromLabA16((quint8*)tmpData, tmpIt2->rawData(), 1); tmpIt2->nextPixel(); matrixIt += 3; } tmpIt2->nextRow(); } } painter()->bitBltWithFixedSelection(dstRect.x(), dstRect.y(), m_srcdev, dab, dstRect.width(), dstRect.height()); painter()->renderMirrorMaskSafe(dstRect, m_srcdev, 0, 0, dab, !m_dabCache->needSeparateOriginal()); return effectiveSpacing(scale); } KisSpacingInformation KisDuplicateOp::updateSpacingImpl(const KisPaintInformation &info) const { return effectiveSpacing(m_sizeOption.apply(info)); } diff --git a/plugins/paintops/deform/kis_deform_paintop.cpp b/plugins/paintops/deform/kis_deform_paintop.cpp index f1e85ee74d..1bde615ba4 100644 --- a/plugins/paintops/deform/kis_deform_paintop.cpp +++ b/plugins/paintops/deform/kis_deform_paintop.cpp @@ -1,156 +1,160 @@ /* * Copyright (c) 2008-2010 Lukáš Tvrdý * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_deform_paintop.h" #include "kis_deform_paintop_settings.h" #include #include #include #include #include #include "kis_global.h" #include "kis_paint_device.h" #include "kis_painter.h" #include "kis_selection.h" #include "kis_random_accessor_ng.h" #include "kis_lod_transform.h" #include #include "kis_deform_option.h" #include "kis_brush_size_option.h" #include "kis_paintop_plugin_utils.h" #include #include #ifdef Q_OS_WIN // quoting DRAND48(3) man-page: // These functions are declared obsolete by SVID 3, // which states that rand(3) should be used instead. #define drand48() (static_cast(qrand()) / static_cast(RAND_MAX)) #endif KisDeformPaintOp::KisDeformPaintOp(const KisPaintOpSettingsSP settings, KisPainter * painter, KisNodeSP node, KisImageSP image) : KisPaintOp(painter) { Q_UNUSED(image); Q_UNUSED(node); Q_ASSERT(settings); m_sizeProperties.readOptionSetting(settings); m_properties.readOptionSetting(settings); m_airbrushOption.readOptionSetting(settings); // sensors m_sizeOption.readOptionSetting(settings); m_opacityOption.readOptionSetting(settings); m_rotationOption.readOptionSetting(settings); m_rateOption.readOptionSetting(settings); m_sizeOption.resetAllSensors(); m_opacityOption.resetAllSensors(); m_rotationOption.resetAllSensors(); m_rateOption.resetAllSensors(); m_deformBrush.setProperties(&m_properties); m_deformBrush.setSizeProperties(&m_sizeProperties); m_deformBrush.initDeformAction(); m_dev = source(); if ((m_sizeProperties.brush_diameter * 0.5) > 1) { m_ySpacing = m_xSpacing = m_sizeProperties.brush_diameter * 0.5 * m_sizeProperties.brush_spacing; } else { m_ySpacing = m_xSpacing = 1.0; } m_spacing = m_xSpacing; } KisDeformPaintOp::~KisDeformPaintOp() { } KisSpacingInformation KisDeformPaintOp::paintAt(const KisPaintInformation& info) { if (!painter()) return KisSpacingInformation(m_spacing); if (!m_dev) return KisSpacingInformation(m_spacing); KisFixedPaintDeviceSP dab = cachedDab(source()->compositionSourceColorSpace()); qint32 x; qreal subPixelX; qint32 y; qreal subPixelY; QPointF pt = info.pos(); if (m_sizeProperties.brush_jitter_movement_enabled) { pt.setX(pt.x() + ((m_sizeProperties.brush_diameter * drand48()) - m_sizeProperties.brush_diameter * 0.5) * m_sizeProperties.brush_jitter_movement); pt.setY(pt.y() + ((m_sizeProperties.brush_diameter * drand48()) - m_sizeProperties.brush_diameter * 0.5) * m_sizeProperties.brush_jitter_movement); } qreal rotation = m_rotationOption.apply(info); // Deform Brush is capable of working with zero scale, // so no additional checks for 'zero'ness are needed qreal scale = m_sizeOption.apply(info); rotation += m_sizeProperties.brush_rotation; scale *= m_sizeProperties.brush_scale; QPointF pos = pt - m_deformBrush.hotSpot(scale, rotation); splitCoordinate(pos.x(), &x, &subPixelX); splitCoordinate(pos.y(), &y, &subPixelY); KisFixedPaintDeviceSP mask = m_deformBrush.paintMask(dab, m_dev, scale, rotation, info.pos(), subPixelX, subPixelY, x, y ); // this happens for the first dab of the move mode, we need more information for being able to move if (!mask) { return updateSpacingImpl(info); } quint8 origOpacity = m_opacityOption.apply(painter(), info); painter()->bltFixedWithFixedSelection(x, y, dab, mask, mask->bounds().width() , mask->bounds().height()); painter()->renderMirrorMask(QRect(QPoint(x, y), QSize(mask->bounds().width() , mask->bounds().height())), dab, mask); painter()->setOpacity(origOpacity); return updateSpacingImpl(info); } KisSpacingInformation KisDeformPaintOp::updateSpacingImpl(const KisPaintInformation &info) const { return KisPaintOpPluginUtils::effectiveSpacing(1.0, 1.0, true, 0.0, false, m_spacing, false, 1.0, KisLodTransform::lodToScale(painter()->device()), - &m_airbrushOption, nullptr, &m_rateOption, info); + &m_airbrushOption, nullptr, info); } +KisTimingInformation KisDeformPaintOp::updateTimingImpl(const KisPaintInformation &info) const +{ + return KisPaintOpPluginUtils::effectiveTiming(&m_airbrushOption, &m_rateOption, info); +} diff --git a/plugins/paintops/deform/kis_deform_paintop.h b/plugins/paintops/deform/kis_deform_paintop.h index 2107156d68..b752bdaafa 100644 --- a/plugins/paintops/deform/kis_deform_paintop.h +++ b/plugins/paintops/deform/kis_deform_paintop.h @@ -1,71 +1,73 @@ /* * Copyright (c) 2008,2010 Lukáš Tvrdý * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_DEFORMPAINTOP_H_ #define KIS_DEFORMPAINTOP_H_ #include #include #include #include #include #include #include #include "deform_brush.h" #include "kis_deform_paintop_settings.h" #include "kis_deform_option.h" class KisPainter; class KisDeformPaintOp : public KisPaintOp { public: KisDeformPaintOp(const KisPaintOpSettingsSP settings, KisPainter * painter, KisNodeSP node, KisImageSP image); ~KisDeformPaintOp() override; protected: KisSpacingInformation paintAt(const KisPaintInformation& info) override; KisSpacingInformation updateSpacingImpl(const KisPaintInformation &info) const override; + KisTimingInformation updateTimingImpl(const KisPaintInformation &info) const override; + private: KisPaintDeviceSP m_dab; KisPaintDeviceSP m_dev; DeformBrush m_deformBrush; DeformOption m_properties; BrushSizeOption m_sizeProperties; KisAirbrushOption m_airbrushOption; KisPressureSizeOption m_sizeOption; KisPressureOpacityOption m_opacityOption; KisPressureRotationOption m_rotationOption; KisPressureRateOption m_rateOption; qreal m_xSpacing; qreal m_ySpacing; qreal m_spacing; }; #endif // KIS_DEFORMPAINTOP_H_ diff --git a/plugins/paintops/dynadraw/kis_dyna_paintop.cpp b/plugins/paintops/dynadraw/kis_dyna_paintop.cpp index 2c17ac8ebe..79b7b2880d 100644 --- a/plugins/paintops/dynadraw/kis_dyna_paintop.cpp +++ b/plugins/paintops/dynadraw/kis_dyna_paintop.cpp @@ -1,137 +1,142 @@ /* * Copyright (c) 2009-2010 Lukáš Tvrdý * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_dyna_paintop.h" #include "kis_dyna_paintop_settings.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_dynaop_option.h" #include "filter.h" KisDynaPaintOp::KisDynaPaintOp(const KisPaintOpSettingsSP settings, KisPainter * painter, KisNodeSP node, KisImageSP image) : KisPaintOp(painter) { Q_UNUSED(node); if (image) { m_dynaBrush.setCanvasSize(image->width(), image->height()); } else { // some dummy values for scratchpad m_dynaBrush.setCanvasSize(1000, 1000); } m_properties.initWidth = settings->getDouble(DYNA_WIDTH); m_properties.action = settings->getDouble(DYNA_ACTION); m_properties.mass = settings->getDouble(DYNA_MASS); m_properties.drag = settings->getDouble(DYNA_DRAG); double angle = settings->getDouble(DYNA_ANGLE); m_properties.xAngle = cos(angle * M_PI / 180.0); m_properties.yAngle = sin(angle * M_PI / 180.0); m_properties.widthRange = settings->getDouble(DYNA_WIDTH_RANGE); m_properties.diameter = settings->getInt(DYNA_DIAMETER); m_properties.lineCount = settings->getInt(DYNA_LINE_COUNT); m_properties.lineSpacing = settings->getDouble(DYNA_LINE_SPACING); m_properties.enableLine = settings->getBool(DYNA_ENABLE_LINE); m_properties.useTwoCircles = settings->getBool(DYNA_USE_TWO_CIRCLES); m_properties.useFixedAngle = settings->getBool(DYNA_USE_FIXED_ANGLE); m_dynaBrush.setProperties(&m_properties); m_airbrushOption.readOptionSetting(settings); m_rateOption.readOptionSetting(settings); m_rateOption.resetAllSensors(); } KisDynaPaintOp::~KisDynaPaintOp() { } void KisDynaPaintOp::paintLine(const KisPaintInformation &pi1, const KisPaintInformation &pi2, KisDistanceInformation *currentDistance) { // Use superclass behavior for lines of zero length. Otherwise, airbrushing can happen faster // than it is supposed to. if (pi1.pos() == pi2.pos()) { KisPaintOp::paintLine(pi1, pi2, currentDistance); } else { doPaintLine(pi1, pi2); } } void KisDynaPaintOp::doPaintLine(const KisPaintInformation &pi1, const KisPaintInformation &pi2) { Q_UNUSED(pi2); if (!painter()) return; if (!m_dab) { m_dab = source()->createCompositionSourceDevice(); } else { m_dab->clear(); } qreal x1, y1; x1 = pi1.pos().x(); y1 = pi1.pos().y(); m_dynaBrush.updateCursorPosition(pi1.pos()); m_dynaBrush.paint(m_dab, x1, y1, painter()->paintColor()); QRect rc = m_dab->extent(); painter()->bitBlt(rc.topLeft(), m_dab, rc); painter()->renderMirrorMask(rc, m_dab); } KisSpacingInformation KisDynaPaintOp::paintAt(const KisPaintInformation& info) { doPaintLine(info, info); return updateSpacingImpl(info); } KisSpacingInformation KisDynaPaintOp::updateSpacingImpl(const KisPaintInformation &info) const { return KisPaintOpPluginUtils::effectiveSpacing(0.0, 0.0, true, 0.0, false, 0.0, false, 0.0, KisLodTransform::lodToScale(painter()->device()), - &m_airbrushOption, nullptr, &m_rateOption, info); + &m_airbrushOption, nullptr, info); +} + +KisTimingInformation KisDynaPaintOp::updateTimingImpl(const KisPaintInformation &info) const +{ + return KisPaintOpPluginUtils::effectiveTiming(&m_airbrushOption, &m_rateOption, info); } diff --git a/plugins/paintops/dynadraw/kis_dyna_paintop.h b/plugins/paintops/dynadraw/kis_dyna_paintop.h index bea66cad71..05e89d02de 100644 --- a/plugins/paintops/dynadraw/kis_dyna_paintop.h +++ b/plugins/paintops/dynadraw/kis_dyna_paintop.h @@ -1,64 +1,66 @@ /* * Copyright (c) 2009-2010 Lukáš Tvrdý * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_DYNA_PAINTOP_H_ #define KIS_DYNA_PAINTOP_H_ #include #include #include #include "kis_airbrush_option.h" #include "kis_pressure_rate_option.h" #include "dyna_brush.h" class KisPainter; class KisDynaPaintOpSettings; class KisDynaPaintOp : public KisPaintOp { public: KisDynaPaintOp(const KisPaintOpSettingsSP settings, KisPainter * painter, KisNodeSP node, KisImageSP image); ~KisDynaPaintOp() override; void paintLine(const KisPaintInformation &pi1, const KisPaintInformation &pi2, KisDistanceInformation *currentDistance) override; virtual bool incremental() const { return true; } protected: KisSpacingInformation paintAt(const KisPaintInformation& info) override; KisSpacingInformation updateSpacingImpl(const KisPaintInformation &info) const override; + KisTimingInformation updateTimingImpl(const KisPaintInformation &info) const override; + private: void doPaintLine(const KisPaintInformation &pi1, const KisPaintInformation &pi2); private: KisDynaProperties m_properties; KisPaintDeviceSP m_dab; DynaBrush m_dynaBrush; KisAirbrushOption m_airbrushOption; KisPressureRateOption m_rateOption; }; #endif // KIS_DYNA_PAINTOP_H_ diff --git a/plugins/paintops/experiment/kis_experiment_paintop.cpp b/plugins/paintops/experiment/kis_experiment_paintop.cpp index 64cdf895c7..ea0efb2532 100644 --- a/plugins/paintops/experiment/kis_experiment_paintop.cpp +++ b/plugins/paintops/experiment/kis_experiment_paintop.cpp @@ -1,341 +1,342 @@ /* * Copyright (c) 2010-2011 Lukáš Tvrdý * Copyright (c) 2012 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_experiment_paintop.h" #include "kis_experiment_paintop_settings.h" #include #include #include #include #include #include +#include #include KisExperimentPaintOp::KisExperimentPaintOp(const KisPaintOpSettingsSP settings, KisPainter *painter, KisNodeSP node, KisImageSP image) : KisPaintOp(painter) { Q_UNUSED(image); Q_UNUSED(node); m_firstRun = true; m_experimentOption.readOptionSetting(settings); m_displaceEnabled = m_experimentOption.isDisplacementEnabled; m_displaceCoeff = (m_experimentOption.displacement * 0.01 * 14) + 1; // 1..15 [7 default according alchemy] m_speedEnabled = m_experimentOption.isSpeedEnabled; m_speedMultiplier = (m_experimentOption.speed * 0.01 * 35); // 0..35 [15 default according alchemy] m_smoothingEnabled = m_experimentOption.isSmoothingEnabled; m_smoothingThreshold = m_experimentOption.smoothing; m_useMirroring = painter->hasMirroring(); m_windingFill = m_experimentOption.windingFill; m_hardEdge = m_experimentOption.hardEdge; if (m_useMirroring) { m_originalDevice = source()->createCompositionSourceDevice(); m_originalPainter = new KisPainter(m_originalDevice); m_originalPainter->setCompositeOp(COMPOSITE_COPY); m_originalPainter->setPaintColor(painter->paintColor()); m_originalPainter->setFillStyle(KisPainter::FillStyleForegroundColor); } else { m_originalPainter = 0; } } KisExperimentPaintOp::~KisExperimentPaintOp() { delete m_originalPainter; } void KisExperimentPaintOp::paintRegion(const QRegion &changedRegion) { if (m_windingFill) { m_path.setFillRule(Qt::WindingFill); } if (m_useMirroring) { m_originalPainter->setAntiAliasPolygonFill(!m_hardEdge); Q_FOREACH (const QRect & rect, changedRegion.rects()) { m_originalPainter->fillPainterPath(m_path, rect); painter()->renderDabWithMirroringNonIncremental(rect, m_originalDevice); } } else { painter()->setFillStyle(KisPainter::FillStyleForegroundColor); painter()->setCompositeOp(COMPOSITE_COPY); painter()->setAntiAliasPolygonFill(!m_hardEdge); Q_FOREACH (const QRect & rect, changedRegion.rects()) { painter()->fillPainterPath(m_path, rect); } } } QPointF KisExperimentPaintOp::speedCorrectedPosition(const KisPaintInformation& pi1, const KisPaintInformation& pi2) { const qreal fadeFactor = 0.6; QPointF diff = pi2.pos() - pi1.pos(); qreal realLength = sqrt(diff.x() * diff.x() + diff.y() * diff.y()); if (realLength < 0.1) return pi2.pos(); qreal coeff = 0.5 * realLength * m_speedMultiplier; m_savedSpeedCoeff = fadeFactor * m_savedSpeedCoeff + (1 - fadeFactor) * coeff; QPointF newPoint = pi1.pos() + diff * m_savedSpeedCoeff / realLength; m_savedSpeedPoint = fadeFactor * m_savedSpeedPoint + (1 - fadeFactor) * newPoint; return m_savedSpeedPoint; } void KisExperimentPaintOp::paintLine(const KisPaintInformation &pi1, const KisPaintInformation &pi2, KisDistanceInformation *currentDistance) { Q_UNUSED(currentDistance); if (!painter()) return; if (m_firstRun) { m_firstRun = false; m_path.moveTo(pi1.pos()); m_path.lineTo(pi2.pos()); m_center = pi1.pos(); m_savedUpdateDistance = 0; m_lastPaintTime = 0; m_savedSpeedCoeff = 0; m_savedSpeedPoint = m_center; m_savedSmoothingDistance = 0; m_savedSmoothingPoint = m_center; } else { const QPointF pos1 = pi1.pos(); QPointF pos2 = pi2.pos(); if (m_speedEnabled) { pos2 = speedCorrectedPosition(pi1, pi2); } int length = (pos2 - pos1).manhattanLength(); m_savedUpdateDistance += length; if (m_smoothingEnabled) { m_savedSmoothingDistance += length; if (m_savedSmoothingDistance > m_smoothingThreshold) { QPointF pt = (m_savedSmoothingPoint + pos2) * 0.5; // for updates approximate curve with two lines m_savedPoints << m_path.currentPosition(); m_savedPoints << m_savedSmoothingPoint; m_savedPoints << m_savedSmoothingPoint; m_savedPoints << pt; m_path.quadTo(m_savedSmoothingPoint, pt); m_savedSmoothingPoint = pos2; m_savedSmoothingDistance = 0; } } else { m_path.lineTo(pos2); m_savedPoints << pos1; m_savedPoints << pos2; } if (m_displaceEnabled) { if (m_path.elementCount() % 16 == 0) { QRectF bounds = m_path.boundingRect(); m_path = applyDisplace(m_path, m_displaceCoeff - length); bounds |= m_path.boundingRect(); qreal threshold = simplifyThreshold(bounds); m_path = KritaUtils::trySimplifyPath(m_path, threshold); } else { m_path = applyDisplace(m_path, m_displaceCoeff - length); } } /** * Refresh rate at least 25fps */ const int timeThreshold = 40; const int elapsedTime = pi2.currentTime() - m_lastPaintTime; QRect pathBounds = m_path.boundingRect().toRect(); int distanceMetric = qMax(pathBounds.width(), pathBounds.height()); if (elapsedTime > timeThreshold || (!m_displaceEnabled && m_savedUpdateDistance > distanceMetric / 8)) { if (m_displaceEnabled) { /** * Rendering the path with diff'ed rects is up to two * times more efficient for really huge shapes (tested * on 2000+ px shapes), however for smaller ones doing * paths arithmetics eats too much time. That's why we * choose the method on the base of the size of the * shape. */ const int pathSizeThreshold = 128; QRegion changedRegion; if (distanceMetric < pathSizeThreshold) { QRectF changedRect = m_path.boundingRect().toRect() | m_lastPaintedPath.boundingRect().toRect(); changedRect.adjust(-1, -1, 1, 1); changedRegion = changedRect.toRect(); } else { QPainterPath diff1 = m_path - m_lastPaintedPath; QPainterPath diff2 = m_lastPaintedPath - m_path; changedRegion = KritaUtils::splitPath(diff1 | diff2); } paintRegion(changedRegion); m_lastPaintedPath = m_path; } else if (!m_savedPoints.isEmpty()) { QRegion changedRegion = KritaUtils::splitTriangles(m_center, m_savedPoints); paintRegion(changedRegion); } m_savedPoints.clear(); m_savedUpdateDistance = 0; m_lastPaintTime = pi2.currentTime(); } } } KisSpacingInformation KisExperimentPaintOp::paintAt(const KisPaintInformation& info) { return updateSpacingImpl(info); } KisSpacingInformation KisExperimentPaintOp::updateSpacingImpl(const KisPaintInformation &info) const { Q_UNUSED(info); return KisSpacingInformation(1.0); } bool tryMergePoints(QPainterPath &path, const QPointF &startPoint, const QPointF &endPoint, qreal &distance, qreal distanceThreshold, bool lastSegment) { qreal length = (endPoint - startPoint).manhattanLength(); if (lastSegment || length > distanceThreshold) { if (distance != 0) { path.lineTo(startPoint); } distance = 0; return false; } distance += length; if (distance > distanceThreshold) { path.lineTo(endPoint); distance = 0; } return true; } qreal KisExperimentPaintOp::simplifyThreshold(const QRectF &bounds) { qreal maxDimension = qMax(bounds.width(), bounds.height()); return qMax(0.01 * maxDimension, 1.0); } QPointF KisExperimentPaintOp::getAngle(const QPointF& p1, const QPointF& p2, qreal distance) { QPointF diff = p1 - p2; qreal realLength = sqrt(diff.x() * diff.x() + diff.y() * diff.y()); return realLength > 0.5 ? p1 + diff * distance / realLength : p1; } QPainterPath KisExperimentPaintOp::applyDisplace(const QPainterPath& path, int speed) { QPointF lastPoint = path.currentPosition(); QPainterPath newPath; int count = path.elementCount(); int curveElementCounter = 0; QPointF ctrl1; QPointF ctrl2; QPointF endPoint; for (int i = 0; i < count; i++) { QPainterPath::Element e = path.elementAt(i); switch (e.type) { case QPainterPath::MoveToElement: { newPath.moveTo(getAngle(QPointF(e.x, e.y), lastPoint, speed)); break; } case QPainterPath::LineToElement: { newPath.lineTo(getAngle(QPointF(e.x, e.y), lastPoint, speed)); break; } case QPainterPath::CurveToElement: { curveElementCounter = 0; endPoint = getAngle(QPointF(e.x, e.y), lastPoint, speed); break; } case QPainterPath::CurveToDataElement: { curveElementCounter++; if (curveElementCounter == 1) { ctrl1 = getAngle(QPointF(e.x, e.y), lastPoint, speed); } else if (curveElementCounter == 2) { ctrl2 = getAngle(QPointF(e.x, e.y), lastPoint, speed); newPath.cubicTo(ctrl1, ctrl2, endPoint); } break; } } }// for return newPath; } diff --git a/plugins/paintops/filterop/kis_filterop.cpp b/plugins/paintops/filterop/kis_filterop.cpp index 22bb27c4ef..36acc4ae76 100644 --- a/plugins/paintops/filterop/kis_filterop.cpp +++ b/plugins/paintops/filterop/kis_filterop.cpp @@ -1,149 +1,150 @@ /* * Copyright (c) 2002 Patrick Julien * Copyright (c) 2004-2008 Boudewijn Rempt * Copyright (c) 2004 Clarence Dang * Copyright (c) 2004 Adrian Page * Copyright (c) 2004 Cyrille Berger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_filterop.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include +#include KisFilterOp::KisFilterOp(const KisPaintOpSettingsSP settings, KisPainter *painter, KisNodeSP node, KisImageSP image) : KisBrushBasedPaintOp(settings, painter) , m_filterConfiguration(0) { Q_UNUSED(node); Q_UNUSED(image); Q_ASSERT(settings); Q_ASSERT(painter); m_tmpDevice = source()->createCompositionSourceDevice(); m_sizeOption.readOptionSetting(settings); m_rotationOption.readOptionSetting(settings); m_sizeOption.resetAllSensors(); m_rotationOption.resetAllSensors(); m_filter = KisFilterRegistry::instance()->get(settings->getString(FILTER_ID)); m_filterConfiguration = static_cast(settings.data())->filterConfig(); m_smudgeMode = settings->getBool(FILTER_SMUDGE_MODE); m_rotationOption.applyFanCornersInfo(this); } KisFilterOp::~KisFilterOp() { } KisSpacingInformation KisFilterOp::paintAt(const KisPaintInformation& info) { if (!painter()) { return KisSpacingInformation(1.0); } if (!m_filter) { return KisSpacingInformation(1.0); } if (!source()) { return KisSpacingInformation(1.0); } KisBrushSP brush = m_brush; if (!brush) return KisSpacingInformation(1.0); if (! brush->canPaintFor(info)) return KisSpacingInformation(1.0); qreal scale = m_sizeOption.apply(info); scale *= KisLodTransform::lodToScale(painter()->device()); if (checkSizeTooSmall(scale)) return KisSpacingInformation(); qreal rotation = m_rotationOption.apply(info); KisDabShape shape(scale, 1.0, rotation); static const KoColorSpace *cs = KoColorSpaceRegistry::instance()->alpha8(); static KoColor color(Qt::black, cs); QRect dstRect; KisFixedPaintDeviceSP dab = m_dabCache->fetchDab(cs, color, info.pos(), shape, info, 1.0, &dstRect); if (dstRect.isEmpty()) return KisSpacingInformation(1.0); QRect dabRect = dab->bounds(); // sanity check Q_ASSERT(dstRect.size() == dabRect.size()); // Filter the paint device QRect neededRect = m_filter->neededRect(dstRect, m_filterConfiguration, painter()->device()->defaultBounds()->currentLevelOfDetail()); KisPainter p(m_tmpDevice); if (!m_smudgeMode) { p.setCompositeOp(COMPOSITE_COPY); } p.bitBltOldData(neededRect.topLeft() - dstRect.topLeft(), source(), neededRect); KisTransaction transaction(m_tmpDevice); m_filter->process(m_tmpDevice, dabRect, m_filterConfiguration, 0); transaction.end(); painter()->bitBltWithFixedSelection(dstRect.x(), dstRect.y(), m_tmpDevice, dab, 0, 0, dabRect.x(), dabRect.y(), dabRect.width(), dabRect.height()); painter()->renderMirrorMaskSafe(dstRect, m_tmpDevice, 0, 0, dab, !m_dabCache->needSeparateOriginal()); return effectiveSpacing(scale, rotation, info); } KisSpacingInformation KisFilterOp::updateSpacingImpl(const KisPaintInformation &info) const { const qreal scale = m_sizeOption.apply(info) * KisLodTransform::lodToScale(painter()->device()); const qreal rotation = m_rotationOption.apply(info); return effectiveSpacing(scale, rotation, info); } diff --git a/plugins/paintops/gridbrush/kis_grid_paintop.cpp b/plugins/paintops/gridbrush/kis_grid_paintop.cpp index fba1119f60..f7567820bf 100644 --- a/plugins/paintops/gridbrush/kis_grid_paintop.cpp +++ b/plugins/paintops/gridbrush/kis_grid_paintop.cpp @@ -1,266 +1,267 @@ /* * Copyright (c) 2009,2010 Lukáš Tvrdý (lukast.dev@gmail.com) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_grid_paintop.h" #include "kis_grid_paintop_settings.h" #include #include #include #include #include #include #include #include #include #include +#include #include #include #include #include #include #include #include #ifdef BENCHMARK #include #endif KisGridPaintOp::KisGridPaintOp(const KisPaintOpSettingsSP settings, KisPainter *painter, KisNodeSP node, KisImageSP image) : KisPaintOp(painter) , m_settings(static_cast(const_cast(settings.data()))) , m_image(image) , m_node(node) { m_properties.readOptionSetting(settings); m_colorProperties.fillProperties(settings); m_xSpacing = m_properties.gridWidth * m_properties.scale; m_ySpacing = m_properties.gridHeight * m_properties.scale; m_spacing = m_xSpacing; m_dab = source()->createCompositionSourceDevice(); m_painter = new KisPainter(m_dab); m_painter->setPaintColor(painter->paintColor()); m_painter->setFillStyle(KisPainter::FillStyleForegroundColor); #ifdef BENCHMARK m_count = m_total = 0; #endif } KisGridPaintOp::~KisGridPaintOp() { delete m_painter; } KisSpacingInformation KisGridPaintOp::paintAt(const KisPaintInformation& info) { #ifdef BENCHMARK QTime time; time.start(); #endif KisRandomSourceSP randomSource = info.randomSource(); const qreal additionalScale = KisLodTransform::lodToScale(painter()->device()); if (!painter()) return KisSpacingInformation(m_spacing * additionalScale); m_dab->clear(); qreal gridWidth = m_properties.gridWidth * m_properties.scale * additionalScale; qreal gridHeight = m_properties.gridHeight * m_properties.scale * additionalScale; int divide; if (m_properties.pressureDivision) { divide = m_properties.divisionLevel * info.pressure(); } else { divide = m_properties.divisionLevel; } divide = qRound(m_properties.scale * divide); qreal posX = info.pos().x(); qreal posY = info.pos().y(); posX = posX - std::fmod(posX, gridWidth); posY = posY - std::fmod(posY, gridHeight); const QRectF dabRect(posX, posY, gridWidth, gridHeight); const QRect dabRectAligned = dabRect.toAlignedRect(); divide = qMax(1, divide); const qreal yStep = gridHeight / (qreal)divide; const qreal xStep = gridWidth / (qreal)divide; QRectF tile; KoColor color(painter()->paintColor()); QScopedPointer colorPicker; if (m_node) { colorPicker.reset(new KisCrossDeviceColorPicker(m_node->paintDevice(), color)); } qreal vertBorder = m_properties.vertBorder * additionalScale; qreal horzBorder = m_properties.horizBorder * additionalScale; if (m_properties.randomBorder) { if (vertBorder == horzBorder) { vertBorder = horzBorder = vertBorder * randomSource->generateNormalized(); } else { vertBorder *= randomSource->generateNormalized(); horzBorder *= randomSource->generateNormalized(); } } bool shouldColor = true; // fill the tile if (m_colorProperties.fillBackground) { m_dab->fill(dabRectAligned, painter()->backgroundColor()); } for (int y = 0; y < divide; y++) { for (int x = 0; x < divide; x++) { // determine the tile size tile = QRectF(dabRect.x() + x * xStep, dabRect.y() + y * yStep, xStep, yStep); tile.adjust(vertBorder, horzBorder, -vertBorder, -horzBorder); tile = tile.normalized(); // do color transformation if (shouldColor) { if (colorPicker && m_colorProperties.sampleInputColor) { colorPicker->pickOldColor(tile.center().x(), tile.center().y(), color.data()); } // mix the color with background color if (m_colorProperties.mixBgColor) { KoMixColorsOp * mixOp = source()->colorSpace()->mixColorsOp(); const quint8 *colors[2]; colors[0] = color.data(); colors[1] = painter()->backgroundColor().data(); qint16 colorWeights[2]; int MAX_16BIT = 255; qreal blend = info.pressure(); colorWeights[0] = static_cast(blend * MAX_16BIT); colorWeights[1] = static_cast((1.0 - blend) * MAX_16BIT); mixOp->mixColors(colors, colorWeights, 2, color.data()); } if (m_colorProperties.useRandomHSV) { QHash params; params["h"] = (m_colorProperties.hue / 180.0) * randomSource->generateNormalized(); params["s"] = (m_colorProperties.saturation / 100.0) * randomSource->generateNormalized(); params["v"] = (m_colorProperties.value / 100.0) * randomSource->generateNormalized(); KoColorTransformation* transfo; transfo = m_dab->colorSpace()->createColorTransformation("hsv_adjustment", params); transfo->setParameter(3, 1);//sets the type to HSV. For some reason 0 is not an option. transfo->setParameter(4, false);//sets the colorize to false. transfo->transform(color.data(), color.data() , 1); } if (m_colorProperties.useRandomOpacity) { const qreal alpha = randomSource->generateNormalized(); color.setOpacity(alpha); m_painter->setOpacity(qRound(alpha * OPACITY_OPAQUE_U8)); } if (!m_colorProperties.colorPerParticle) { shouldColor = false; } m_painter->setPaintColor(color); } // paint some element switch (m_properties.shape) { case 0: { m_painter->paintEllipse(tile); break; } case 1: { // anti-aliased version //m_painter->paintRect(tile); m_dab->fill(tile.topLeft().x(), tile.topLeft().y(), tile.width(), tile.height(), color.data()); break; } case 2: { m_painter->drawDDALine(tile.topRight(), tile.bottomLeft()); break; } case 3: { m_painter->drawLine(tile.topRight(), tile.bottomLeft()); break; } case 4: { m_painter->drawThickLine(tile.topRight(), tile.bottomLeft() , 1, 10); break; } default: { break; } } if (m_colorProperties.colorPerParticle){ color=painter()->paintColor();//reset color// } } } QRect rc = m_dab->extent(); painter()->bitBlt(rc.topLeft(), m_dab, rc); painter()->renderMirrorMask(rc, m_dab); #ifdef BENCHMARK int msec = time.elapsed(); dbgKrita << msec << " ms/dab " << "[average: " << m_total / (qreal)m_count << "]"; m_total += msec; m_count++; #endif return computeSpacing(additionalScale); } KisSpacingInformation KisGridPaintOp::updateSpacingImpl(const KisPaintInformation &info) const { Q_UNUSED(info); return computeSpacing(KisLodTransform::lodToScale(painter()->device())); } KisSpacingInformation KisGridPaintOp::computeSpacing(qreal lodScale) const { return KisSpacingInformation(m_spacing * lodScale); } void KisGridProperties::readOptionSetting(const KisPropertiesConfigurationSP setting) { gridWidth = qMax(1, setting->getInt(GRID_WIDTH)); gridHeight = qMax(1, setting->getInt(GRID_HEIGHT)); divisionLevel = qMax(1, setting->getInt(GRID_DIVISION_LEVEL)); pressureDivision = setting->getBool(GRID_PRESSURE_DIVISION); randomBorder = setting->getBool(GRID_RANDOM_BORDER); scale = qMax(0.1, setting->getDouble(GRID_SCALE)); vertBorder = setting->getDouble(GRID_VERTICAL_BORDER); horizBorder = setting->getDouble(GRID_HORIZONTAL_BORDER); shape = setting->getInt(GRIDSHAPE_SHAPE); } diff --git a/plugins/paintops/hairy/kis_hairy_paintop.cpp b/plugins/paintops/hairy/kis_hairy_paintop.cpp index 8db657b274..ad592f3a24 100644 --- a/plugins/paintops/hairy/kis_hairy_paintop.cpp +++ b/plugins/paintops/hairy/kis_hairy_paintop.cpp @@ -1,143 +1,144 @@ /* * Copyright (c) 2008-2010 Lukáš Tvrdý * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_hairy_paintop.h" #include "kis_hairy_paintop_settings.h" #include #include #include #include #include "kis_paint_device.h" #include "kis_painter.h" #include #include #include #include #include #include #include +#include #include "kis_brush.h" KisHairyPaintOp::KisHairyPaintOp(const KisPaintOpSettingsSP settings, KisPainter * painter, KisNodeSP node, KisImageSP image) : KisPaintOp(painter) { Q_UNUSED(image) Q_ASSERT(settings); m_dev = node ? node->paintDevice() : 0; KisBrushOption brushOption; brushOption.readOptionSettingForceCopy(settings); KisBrushSP brush = brushOption.brush(); KisFixedPaintDeviceSP dab = cachedDab(painter->device()->compositionSourceColorSpace()); if (brush->brushType() == IMAGE || brush->brushType() == PIPE_IMAGE) { dab = brush->paintDevice(source()->colorSpace(), KisDabShape(), KisPaintInformation()); } else { brush->mask(dab, painter->paintColor(), KisDabShape(), KisPaintInformation()); } m_brush.fromDabWithDensity(dab, settings->getDouble(HAIRY_BRISTLE_DENSITY) * 0.01); m_brush.setInkColor(painter->paintColor()); loadSettings(static_cast(settings.data())); m_brush.setProperties(&m_properties); m_rotationOption.readOptionSetting(settings); m_opacityOption.readOptionSetting(settings); m_sizeOption.readOptionSetting(settings); m_rotationOption.resetAllSensors(); m_opacityOption.resetAllSensors(); m_sizeOption.resetAllSensors(); } void KisHairyPaintOp::loadSettings(const KisBrushBasedPaintOpSettings *settings) { m_properties.inkAmount = settings->getInt(HAIRY_INK_AMOUNT); //TODO: wait for the transfer function with variable size m_properties.inkDepletionCurve = settings->getCubicCurve(HAIRY_INK_DEPLETION_CURVE).floatTransfer(m_properties.inkAmount); m_properties.inkDepletionEnabled = settings->getBool(HAIRY_INK_DEPLETION_ENABLED); m_properties.useSaturation = settings->getBool(HAIRY_INK_USE_SATURATION); m_properties.useOpacity = settings->getBool(HAIRY_INK_USE_OPACITY); m_properties.useWeights = settings->getBool(HAIRY_INK_USE_WEIGHTS); m_properties.pressureWeight = settings->getDouble(HAIRY_INK_PRESSURE_WEIGHT) / 100.0; m_properties.bristleLengthWeight = settings->getDouble(HAIRY_INK_BRISTLE_LENGTH_WEIGHT) / 100.0; m_properties.bristleInkAmountWeight = settings->getDouble(HAIRY_INK_BRISTLE_INK_AMOUNT_WEIGHT) / 100.0; m_properties.inkDepletionWeight = settings->getDouble(HAIRY_INK_DEPLETION_WEIGHT); m_properties.useSoakInk = settings->getBool(HAIRY_INK_SOAK); m_properties.useMousePressure = settings->getBool(HAIRY_BRISTLE_USE_MOUSEPRESSURE); m_properties.shearFactor = settings->getDouble(HAIRY_BRISTLE_SHEAR); m_properties.randomFactor = settings->getDouble(HAIRY_BRISTLE_RANDOM); m_properties.scaleFactor = settings->getDouble(HAIRY_BRISTLE_SCALE); m_properties.threshold = settings->getBool(HAIRY_BRISTLE_THRESHOLD); m_properties.antialias = settings->getBool(HAIRY_BRISTLE_ANTI_ALIASING); m_properties.useCompositing = settings->getBool(HAIRY_BRISTLE_USE_COMPOSITING); m_properties.connectedPath = settings->getBool(HAIRY_BRISTLE_CONNECTED); } KisSpacingInformation KisHairyPaintOp::paintAt(const KisPaintInformation& info) { return updateSpacingImpl(info); } KisSpacingInformation KisHairyPaintOp::updateSpacingImpl(const KisPaintInformation &info) const { Q_UNUSED(info); return KisSpacingInformation(0.5); } void KisHairyPaintOp::paintLine(const KisPaintInformation &pi1, const KisPaintInformation &pi2, KisDistanceInformation *currentDistance) { Q_UNUSED(currentDistance); if (!painter()) return; if (!m_dab) { m_dab = source()->createCompositionSourceDevice(); } else { m_dab->clear(); } // Hairy Brush is capable of working with zero scale, // so no additional checks for 'zero'ness are needed qreal scale = m_sizeOption.apply(pi2); scale *= KisLodTransform::lodToScale(painter()->device()); qreal rotation = m_rotationOption.apply(pi2); quint8 origOpacity = m_opacityOption.apply(painter(), pi2); m_brush.paintLine(m_dab, m_dev, pi1, pi2, scale * m_properties.scaleFactor, rotation); //QRect rc = m_dab->exactBounds(); QRect rc = m_dab->extent(); painter()->bitBlt(rc.topLeft(), m_dab, rc); painter()->renderMirrorMask(rc, m_dab); painter()->setOpacity(origOpacity); } diff --git a/plugins/paintops/hatching/kis_hatching_paintop.cpp b/plugins/paintops/hatching/kis_hatching_paintop.cpp index 453bd834db..8d95873b32 100644 --- a/plugins/paintops/hatching/kis_hatching_paintop.cpp +++ b/plugins/paintops/hatching/kis_hatching_paintop.cpp @@ -1,209 +1,210 @@ /* * Copyright (c) 2008,2009 Lukáš Tvrdý * Copyright (c) 2010 José Luis Vergara * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_hatching_paintop.h" #include "kis_hatching_paintop_settings.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include +#include #include KisHatchingPaintOp::KisHatchingPaintOp(const KisPaintOpSettingsSP settings, KisPainter * painter, KisNodeSP node, KisImageSP image) : KisBrushBasedPaintOp(settings, painter) , m_image(image) { Q_UNUSED(node); m_settings = new KisHatchingPaintOpSettings(); static_cast(settings.data())->initializeTwin(m_settings); m_hatchingBrush = new HatchingBrush(m_settings); m_crosshatchingOption.readOptionSetting(settings); m_separationOption.readOptionSetting(settings); m_thicknessOption.readOptionSetting(settings); m_opacityOption.readOptionSetting(settings); m_sizeOption.readOptionSetting(settings); m_crosshatchingOption.resetAllSensors(); m_separationOption.resetAllSensors(); m_thicknessOption.resetAllSensors(); m_opacityOption.resetAllSensors(); m_sizeOption.resetAllSensors(); } KisHatchingPaintOp::~KisHatchingPaintOp() { delete m_hatchingBrush; } KisSpacingInformation KisHatchingPaintOp::paintAt(const KisPaintInformation& info) { //------START SIMPLE ERROR CATCHING------- if (!painter()->device()) return KisSpacingInformation(1.0); if (!m_hatchedDab) m_hatchedDab = source()->createCompositionSourceDevice(); else m_hatchedDab->clear(); //Simple convenience renaming, I'm thinking of removing these inherited quirks KisBrushSP brush = m_brush; KisPaintDeviceSP device = painter()->device(); //Macro to catch errors Q_ASSERT(brush); //----------SIMPLE error catching code, maybe it's not even needed------ if (!brush) return KisSpacingInformation(1.0); if (!brush->canPaintFor(info)) return KisSpacingInformation(1.0); //SENSOR-depending settings m_settings->crosshatchingsensorvalue = m_crosshatchingOption.apply(info); m_settings->separationsensorvalue = m_separationOption.apply(info); m_settings->thicknesssensorvalue = m_thicknessOption.apply(info); const qreal additionalScale = KisLodTransform::lodToScale(painter()->device()); const double scale = additionalScale * m_sizeOption.apply(info); if ((scale * brush->width()) <= 0.01 || (scale * brush->height()) <= 0.01) return KisSpacingInformation(1.0); KisDabShape shape(scale, 1.0, 0.0); quint8 origOpacity = m_opacityOption.apply(painter(), info); /*----Fetch the Dab----*/ static const KoColorSpace *cs = KoColorSpaceRegistry::instance()->alpha8(); static KoColor color(Qt::black, cs); QRect dstRect; KisFixedPaintDeviceSP maskDab = m_dabCache->fetchDab(cs, color, info.pos(), shape, info, 1.0, &dstRect); // sanity check KIS_ASSERT_RECOVER_NOOP(dstRect.size() == maskDab->bounds().size()); /*-----Convenient renaming for the limits of the maskDab, this will be used to hatch a dab of just the right size------*/ qint32 x, y, sw, sh; dstRect.getRect(&x, &y, &sw, &sh); //------This If_block pre-fills the future m_hatchedDab with a pretty backgroundColor if (m_settings->opaquebackground) { KoColor aersh = painter()->backgroundColor(); m_hatchedDab->fill(0, 0, (sw - 1), (sh - 1), aersh.data()); //this plus yellow background = french fry brush } // Trick for moire pattern to look better bool donotbasehatch = false; /* If block describing how to stack hatching passes to generate crosshatching according to user specifications */ if (m_settings->enabledcurvecrosshatching) { if (m_settings->perpendicular) { if (m_settings->crosshatchingsensorvalue > 0.5) m_hatchingBrush->hatch(m_hatchedDab, x, y, sw, sh, spinAngle(90), painter()->paintColor(), additionalScale); } else if (m_settings->minusthenplus) { if (m_settings->crosshatchingsensorvalue > 0.33) m_hatchingBrush->hatch(m_hatchedDab, x, y, sw, sh, spinAngle(-45), painter()->paintColor(), additionalScale); if (m_settings->crosshatchingsensorvalue > 0.67) m_hatchingBrush->hatch(m_hatchedDab, x, y, sw, sh, spinAngle(45), painter()->paintColor(), additionalScale); } else if (m_settings->plusthenminus) { if (m_settings->crosshatchingsensorvalue > 0.33) m_hatchingBrush->hatch(m_hatchedDab, x, y, sw, sh, spinAngle(45), painter()->paintColor(), additionalScale); if (m_settings->crosshatchingsensorvalue > 0.67) m_hatchingBrush->hatch(m_hatchedDab, x, y, sw, sh, spinAngle(-45), painter()->paintColor(), additionalScale); } else if (m_settings->moirepattern) { m_hatchingBrush->hatch(m_hatchedDab, x, y, sw, sh, spinAngle((m_settings->crosshatchingsensorvalue) * 180), painter()->paintColor(), additionalScale); donotbasehatch = true; } } else { if (m_settings->perpendicular) { m_hatchingBrush->hatch(m_hatchedDab, x, y, sw, sh, spinAngle(90), painter()->paintColor(), additionalScale); } else if (m_settings->minusthenplus) { m_hatchingBrush->hatch(m_hatchedDab, x, y, sw, sh, spinAngle(-45), painter()->paintColor(), additionalScale); m_hatchingBrush->hatch(m_hatchedDab, x, y, sw, sh, spinAngle(45), painter()->paintColor(), additionalScale); } else if (m_settings->plusthenminus) { m_hatchingBrush->hatch(m_hatchedDab, x, y, sw, sh, spinAngle(45), painter()->paintColor(), additionalScale); m_hatchingBrush->hatch(m_hatchedDab, x, y, sw, sh, spinAngle(-45), painter()->paintColor(), additionalScale); } else if (m_settings->moirepattern) { m_hatchingBrush->hatch(m_hatchedDab, x, y, sw, sh, spinAngle(-10), painter()->paintColor(), additionalScale); } } if (!donotbasehatch) m_hatchingBrush->hatch(m_hatchedDab, x, y, sw, sh, m_settings->angle, painter()->paintColor(), additionalScale); // The most important line, the one that paints to the screen. painter()->bitBltWithFixedSelection(x, y, m_hatchedDab, maskDab, sw, sh); painter()->renderMirrorMaskSafe(QRect(QPoint(x, y), QSize(sw, sh)), m_hatchedDab, 0, 0, maskDab, !m_dabCache->needSeparateOriginal()); painter()->setOpacity(origOpacity); return effectiveSpacing(scale); } KisSpacingInformation KisHatchingPaintOp::updateSpacingImpl(const KisPaintInformation &info) const { const qreal scale = KisLodTransform::lodToScale(painter()->device()) * m_sizeOption.apply(info); return effectiveSpacing(scale); } double KisHatchingPaintOp::spinAngle(double spin) { double tempangle = m_settings->angle + spin; qint8 factor = 1; if (tempangle < 0) factor = -1; tempangle = fabs(fmod(tempangle, 180)); if ((tempangle >= 0) && (tempangle <= 90)) return factor * tempangle; else if ((tempangle > 90) && (tempangle <= 180)) return factor * -(180 - tempangle); return 0; // this should never be executed except if NAN } diff --git a/plugins/paintops/libpaintop/kis_brush_based_paintop.cpp b/plugins/paintops/libpaintop/kis_brush_based_paintop.cpp index e2c2a9fd41..a119ae5348 100644 --- a/plugins/paintops/libpaintop/kis_brush_based_paintop.cpp +++ b/plugins/paintops/libpaintop/kis_brush_based_paintop.cpp @@ -1,183 +1,181 @@ /* * Copyright (c) 2008 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_brush_based_paintop.h" #include "kis_properties_configuration.h" #include #include "kis_brush_option.h" #include #include #include "kis_painter.h" #include #include "kis_paintop_utils.h" #include "kis_paintop_plugin_utils.h" #include #include #include #ifdef HAVE_THREADED_TEXT_RENDERING_WORKAROUND Q_GLOBAL_STATIC(TextBrushInitializationWorkaround, s_instance) TextBrushInitializationWorkaround *TextBrushInitializationWorkaround::instance() { return s_instance; } void TextBrushInitializationWorkaround::preinitialize(KisPropertiesConfigurationSP settings) { if (KisBrushOption::isTextBrush(settings.data())) { KisBrushOption brushOption; brushOption.readOptionSettingForceCopy(settings); m_brush = brushOption.brush(); m_settings = settings; } else { m_brush = 0; m_settings = 0; } } KisBrushSP TextBrushInitializationWorkaround::tryGetBrush(const KisPropertiesConfigurationSP settings) { return (settings && settings == m_settings ? m_brush : 0); } TextBrushInitializationWorkaround::TextBrushInitializationWorkaround() : m_settings(0) {} TextBrushInitializationWorkaround::~TextBrushInitializationWorkaround() {} void KisBrushBasedPaintOp::preinitializeOpStatically(KisPaintOpSettingsSP settings) { TextBrushInitializationWorkaround::instance()->preinitialize(settings); } #endif /* HAVE_THREADED_TEXT_RENDERING_WORKAROUND */ KisBrushBasedPaintOp::KisBrushBasedPaintOp(const KisPropertiesConfigurationSP settings, KisPainter* painter) : KisPaintOp(painter), m_textureProperties(painter->device()->defaultBounds()->currentLevelOfDetail()) { Q_ASSERT(settings); #ifdef HAVE_THREADED_TEXT_RENDERING_WORKAROUND m_brush = TextBrushInitializationWorkaround::instance()->tryGetBrush(settings); #endif /* HAVE_THREADED_TEXT_RENDERING_WORKAROUND */ if (!m_brush) { KisBrushOption brushOption; brushOption.readOptionSettingForceCopy(settings); m_brush = brushOption.brush(); } m_brush->notifyStrokeStarted(); m_precisionOption.readOptionSetting(settings); m_dabCache = new KisDabCache(m_brush); m_dabCache->setPrecisionOption(&m_precisionOption); m_mirrorOption.readOptionSetting(settings); m_dabCache->setMirrorPostprocessing(&m_mirrorOption); m_textureProperties.fillProperties(settings); m_dabCache->setTexturePostprocessing(&m_textureProperties); } KisBrushBasedPaintOp::~KisBrushBasedPaintOp() { delete m_dabCache; } bool KisBrushBasedPaintOp::checkSizeTooSmall(qreal scale) { scale *= m_brush->scale(); return KisPaintOpUtils::checkSizeTooSmall(scale, m_brush->width(), m_brush->height()); } KisSpacingInformation KisBrushBasedPaintOp::effectiveSpacing(qreal scale) const { // we parse dab rotation separately, so don't count it QSizeF metric = m_brush->characteristicSize(KisDabShape(scale, 1.0, 0)); return effectiveSpacing(metric.width(), metric.height(), 1.0, false, 0.0, false); } KisSpacingInformation KisBrushBasedPaintOp::effectiveSpacing(qreal scale, qreal rotation, const KisPaintInformation &pi) const { - return effectiveSpacing(scale, rotation, nullptr, nullptr, nullptr, pi); + return effectiveSpacing(scale, rotation, nullptr, nullptr, pi); } KisSpacingInformation KisBrushBasedPaintOp::effectiveSpacing(qreal scale, qreal rotation, const KisPressureSpacingOption &spacingOption, const KisPaintInformation &pi) const { - return effectiveSpacing(scale, rotation, nullptr, &spacingOption, nullptr, pi); + return effectiveSpacing(scale, rotation, nullptr, &spacingOption, pi); } KisSpacingInformation KisBrushBasedPaintOp::effectiveSpacing(qreal scale, qreal rotation, const KisAirbrushOption *airbrushOption, const KisPressureSpacingOption *spacingOption, - const KisPressureRateOption *rateOption, const KisPaintInformation &pi) const { bool isotropicSpacing = spacingOption && spacingOption->isotropicSpacing(); MirrorProperties prop = m_mirrorOption.apply(pi); const bool implicitFlipped = prop.horizontalMirror != prop.verticalMirror; // we parse dab rotation separately, so don't count it QSizeF metric = m_brush->characteristicSize(KisDabShape(scale, 1.0, 0)); return KisPaintOpPluginUtils::effectiveSpacing(metric.width(), metric.height(), isotropicSpacing, rotation, implicitFlipped, m_brush->spacing(), m_brush->autoSpacingActive(), m_brush->autoSpacingCoeff(), KisLodTransform::lodToScale(painter()->device()), - airbrushOption, spacingOption, rateOption, + airbrushOption, spacingOption, pi); } KisSpacingInformation KisBrushBasedPaintOp::effectiveSpacing(qreal dabWidth, qreal dabHeight, qreal extraScale, bool isotropicSpacing, qreal rotation, bool axesFlipped) const { return KisPaintOpUtils::effectiveSpacing(dabWidth, dabHeight, - extraScale, 1.0, + extraScale, true, isotropicSpacing, rotation, axesFlipped, m_brush->spacing(), m_brush->autoSpacingActive(), m_brush->autoSpacingCoeff(), - false, 1000.0, KisLodTransform::lodToScale(painter()->device())); } bool KisBrushBasedPaintOp::canPaint() const { return m_brush != 0; } diff --git a/plugins/paintops/libpaintop/kis_brush_based_paintop.h b/plugins/paintops/libpaintop/kis_brush_based_paintop.h index 518be396e5..305fbf3ecd 100644 --- a/plugins/paintops/libpaintop/kis_brush_based_paintop.h +++ b/plugins/paintops/libpaintop/kis_brush_based_paintop.h @@ -1,102 +1,101 @@ /* * Copyright (c) 2008 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_BRUSH_BASED_PAINTOP_H #define KIS_BRUSH_BASED_PAINTOP_H #include "kritapaintop_export.h" #include #include "kis_dab_cache.h" #include "kis_brush.h" #include "kis_texture_option.h" #include "kis_precision_option.h" #include "kis_airbrush_option.h" #include "kis_pressure_mirror_option.h" #include class KisPropertiesConfiguration; class KisPressureSpacingOption; class KisPressureRateOption; class KisDabCache; /// Internal class TextBrushInitializationWorkaround { public: TextBrushInitializationWorkaround(); ~TextBrushInitializationWorkaround(); static TextBrushInitializationWorkaround* instance(); void preinitialize(KisPropertiesConfigurationSP settings); KisBrushSP tryGetBrush(const KisPropertiesConfigurationSP settings); private: KisBrushSP m_brush; KisPropertiesConfigurationSP m_settings; }; /** * This is a base class for paintops that use a KisBrush or derived * brush to paint with. This is mainly important for the spacing * generation. */ class PAINTOP_EXPORT KisBrushBasedPaintOp : public KisPaintOp { public: KisBrushBasedPaintOp(const KisPropertiesConfigurationSP settings, KisPainter* painter); ~KisBrushBasedPaintOp() override; bool checkSizeTooSmall(qreal scale); KisSpacingInformation effectiveSpacing(qreal scale) const; KisSpacingInformation effectiveSpacing(qreal scale, qreal rotation, const KisPaintInformation &pi) const; KisSpacingInformation effectiveSpacing(qreal scale, qreal rotation, const KisPressureSpacingOption &spacingOption, const KisPaintInformation &pi) const; KisSpacingInformation effectiveSpacing(qreal scale, qreal rotation, const KisAirbrushOption *airbrushOption, const KisPressureSpacingOption *spacingOption, - const KisPressureRateOption *rateOption, const KisPaintInformation &pi) const; ///Reimplemented, false if brush is 0 bool canPaint() const override; #ifdef HAVE_THREADED_TEXT_RENDERING_WORKAROUND typedef int needs_preinitialization; static void preinitializeOpStatically(KisPaintOpSettingsSP settings); #endif /* HAVE_THREADED_TEXT_RENDERING_WORKAROUND */ private: KisSpacingInformation effectiveSpacing(qreal dabWidth, qreal dabHeight, qreal extraScale, bool isotropicSpacing, qreal rotation, bool axesFlipped) const; protected: // XXX: make private! KisDabCache *m_dabCache; KisBrushSP m_brush; private: KisTextureProperties m_textureProperties; KisPressureMirrorOption m_mirrorOption; KisPrecisionOption m_precisionOption; }; #endif diff --git a/plugins/paintops/libpaintop/kis_paintop_plugin_utils.h b/plugins/paintops/libpaintop/kis_paintop_plugin_utils.h index 41b656c1f5..fca903ebe3 100644 --- a/plugins/paintops/libpaintop/kis_paintop_plugin_utils.h +++ b/plugins/paintops/libpaintop/kis_paintop_plugin_utils.h @@ -1,87 +1,101 @@ /* * Copyright (c) 2014 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef __KIS_PAINTOP_PLUGIN_UTILS_H #define __KIS_PAINTOP_PLUGIN_UTILS_H #include "kis_paint_information.h" #include "kis_paintop_utils.h" #include "kis_paintop_settings.h" #include "kis_airbrush_option.h" #include "kis_pressure_spacing_option.h" #include "kis_pressure_rate_option.h" namespace KisPaintOpPluginUtils { /** * Similar to KisPaintOpUtils::effectiveSpacing, but some of the required parameters are obtained * from the provided configuration options. This function assumes a common configuration where - * curve-based spacing and rate options provide pressure sensitivity, and airbrush settings are - * configured through a KisAirbrushOption. This type of configuration is used by several different - * paintops. + * spacing and airbrush settings are configured through a KisPressureSpacingOption and + * KisAirbrushOption. This type of configuration is used by several different paintops. * @param airbrushOption - The airbrushing option. Can be null for paintops that don't support * airbrushing. * @param spacingOption - The pressure-curve spacing option. Can be null for paintops that don't * support pressure-based spacing. - * @param rateOption - The pressure-curve airbrush rate option. Can be null for paintops that don't - * support a pressure-based airbrush rate. */ - KisSpacingInformation effectiveSpacing(qreal dabWidth, qreal dabHeight, bool isotropicSpacing, qreal rotation, bool axesFlipped, qreal spacingVal, bool autoSpacingActive, qreal autoSpacingCoeff, qreal lodScale, const KisAirbrushOption *airbrushOption, const KisPressureSpacingOption *spacingOption, - const KisPressureRateOption *rateOption, const KisPaintInformation &pi) { // Extract required parameters. bool distanceSpacingEnabled = true; - bool timedSpacingEnabled = false; - qreal timedSpacingInterval = 0.0; - if (airbrushOption) { - timedSpacingEnabled = airbrushOption->isChecked(); - distanceSpacingEnabled - = !(timedSpacingEnabled && airbrushOption->ignoreSpacing()); - timedSpacingInterval = airbrushOption->airbrushInterval(); + if (airbrushOption && airbrushOption->isChecked()) { + distanceSpacingEnabled = !airbrushOption->ignoreSpacing(); } qreal extraScale = 1.0; if (spacingOption && spacingOption->isChecked()) { extraScale = spacingOption->apply(pi); } + + return KisPaintOpUtils::effectiveSpacing(dabWidth, dabHeight, extraScale, + distanceSpacingEnabled, isotropicSpacing, rotation, + axesFlipped, spacingVal, autoSpacingActive, + autoSpacingCoeff, lodScale); +} + +/** + * Similar to KisPaintOpUtils::effectiveTiming, but some of the required parameters are obtained + * from the provided configuration options. This function assumes a common configuration where + * airbrush settings are configured through a KisAirbrushOption and KisPressureRateOption. This type + * of configuration is used by several different paintops. + * @param airbrushOption - The airbrushing option. Can be null for paintops that don't support + * airbrushing. + * @param rateOption - The pressure-curve airbrush rate option. Can be null for paintops that don't + * support a pressure-based airbrush rate. + */ +KisTimingInformation effectiveTiming(const KisAirbrushOption *airbrushOption, + const KisPressureRateOption *rateOption, + const KisPaintInformation &pi) +{ + // Extract required parameters. + bool timingEnabled = false; + qreal timingInterval = LONG_TIME; + if (airbrushOption) { + timingEnabled = airbrushOption->isChecked(); + timingInterval = airbrushOption->airbrushInterval(); + } qreal rateExtraScale = 1.0; if (rateOption && rateOption->isChecked()) { rateExtraScale = rateOption->apply(pi); } - return KisPaintOpUtils::effectiveSpacing(dabWidth, dabHeight, extraScale, rateExtraScale, - distanceSpacingEnabled, isotropicSpacing, rotation, - axesFlipped, spacingVal, autoSpacingActive, - autoSpacingCoeff, timedSpacingEnabled, - timedSpacingInterval, lodScale); + return KisPaintOpUtils::effectiveTiming(timingEnabled, timingInterval, rateExtraScale); } } #endif /* __KIS_PAINTOP_PLUGIN_UTILS_H */ diff --git a/plugins/paintops/libpaintop/kis_pressure_spacing_option.cpp b/plugins/paintops/libpaintop/kis_pressure_spacing_option.cpp index 1d182d0e0c..acc8d95561 100644 --- a/plugins/paintops/libpaintop/kis_pressure_spacing_option.cpp +++ b/plugins/paintops/libpaintop/kis_pressure_spacing_option.cpp @@ -1,59 +1,73 @@ /* This file is part of the KDE project * Copyright (c) 2011 Cyrille Berger * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kis_pressure_spacing_option.h" #include +#include "kis_paintop_settings.h" const QString ISOTROPIC_SPACING = "Spacing/Isotropic"; KisPressureSpacingOption::KisPressureSpacingOption() : KisCurveOption("Spacing", KisPaintOpOption::GENERAL, false), - m_isotropicSpacing(false) + m_isotropicSpacing(false), + m_useSpacingUpdates(false) { } double KisPressureSpacingOption::apply(const KisPaintInformation & info) const { if (!isChecked()) return 1.0; return computeSizeLikeValue(info); } void KisPressureSpacingOption::setIsotropicSpacing(bool isotropic) { m_isotropicSpacing = isotropic; } bool KisPressureSpacingOption::isotropicSpacing() const { return m_isotropicSpacing; } +void KisPressureSpacingOption::setUsingSpacingUpdates(bool useUpdates) +{ + m_useSpacingUpdates = useUpdates; +} + +bool KisPressureSpacingOption::usingSpacingUpdates() const +{ + return m_useSpacingUpdates; +} + void KisPressureSpacingOption::writeOptionSetting(KisPropertiesConfigurationSP setting) const { KisCurveOption::writeOptionSetting(setting); setting->setProperty(ISOTROPIC_SPACING, m_isotropicSpacing); + setting->setProperty(SPACING_USE_UPDATES, m_useSpacingUpdates); } void KisPressureSpacingOption::readOptionSetting(const KisPropertiesConfigurationSP setting) { KisCurveOption::readOptionSetting(setting); m_isotropicSpacing = setting->getBool(ISOTROPIC_SPACING, false); + m_useSpacingUpdates = setting->getBool(SPACING_USE_UPDATES, false); } diff --git a/plugins/paintops/libpaintop/kis_pressure_spacing_option.h b/plugins/paintops/libpaintop/kis_pressure_spacing_option.h index b4514e9aa6..05a1134cc6 100644 --- a/plugins/paintops/libpaintop/kis_pressure_spacing_option.h +++ b/plugins/paintops/libpaintop/kis_pressure_spacing_option.h @@ -1,47 +1,58 @@ /* This file is part of the KDE project * Copyright (c) 2011 Cyrille Berger * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KIS_PRESSURE_SPACING_OPTION_H #define KIS_PRESSURE_SPACING_OPTION_H #include "kis_curve_option.h" #include #include /** * The pressure spacing option defines a curve that is used to * calculate the effect of pressure on the spacing of the dab */ class PAINTOP_EXPORT KisPressureSpacingOption : public KisCurveOption { public: KisPressureSpacingOption(); double apply(const KisPaintInformation & info) const; void setIsotropicSpacing(bool isotropic); bool isotropicSpacing() const; + /** + * @param useUpdates True if and only if the spacing option should allow spacing updates between + * painted dabs. + */ + void setUsingSpacingUpdates(bool useUpdates); + /** + * @return True if and only if the spacing option allows spacing updates between painted dabs. + */ + bool usingSpacingUpdates() const; + void readOptionSetting(const KisPropertiesConfigurationSP setting) override; void writeOptionSetting(KisPropertiesConfigurationSP setting) const override; private: bool m_isotropicSpacing; + bool m_useSpacingUpdates; }; #endif diff --git a/plugins/paintops/libpaintop/kis_pressure_spacing_option_widget.cpp b/plugins/paintops/libpaintop/kis_pressure_spacing_option_widget.cpp index 889af5fae1..930b1c4068 100644 --- a/plugins/paintops/libpaintop/kis_pressure_spacing_option_widget.cpp +++ b/plugins/paintops/libpaintop/kis_pressure_spacing_option_widget.cpp @@ -1,59 +1,72 @@ /* * Copyright (c) 2010 Lukáš Tvrdý * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include #include "kis_curve_option_widget.h" #include "kis_pressure_spacing_option.h" #include "kis_pressure_spacing_option_widget.h" KisPressureSpacingOptionWidget::KisPressureSpacingOptionWidget(): KisCurveOptionWidget(new KisPressureSpacingOption(), i18n("0%"), i18n("100%")) { QCheckBox *isotropicSpacing = new QCheckBox(i18n("Isotropic Spacing")); + QCheckBox *useSpacingUpdates = new QCheckBox(i18n("Update Between Dabs")); - QVBoxLayout* vl = new QVBoxLayout; + QHBoxLayout *hl = new QHBoxLayout; + hl->addWidget(isotropicSpacing); + hl->addWidget(useSpacingUpdates); + + QVBoxLayout *vl = new QVBoxLayout; vl->setMargin(0); - vl->addWidget(isotropicSpacing); + vl->addLayout(hl); vl->addWidget(KisCurveOptionWidget::curveWidget()); - QWidget* w = new QWidget; + QWidget *w = new QWidget; w->setLayout(vl); KisCurveOptionWidget::setConfigurationPage(w); connect(isotropicSpacing, SIGNAL(stateChanged(int)), this, SLOT(setIsotropicSpacing(int))); + connect(useSpacingUpdates, SIGNAL(stateChanged(int)), + this, SLOT(setUseSpacingUpdates(int))); setIsotropicSpacing(false); } void KisPressureSpacingOptionWidget::setIsotropicSpacing(int isotropic) { dynamic_cast(KisCurveOptionWidget::curveOption())->setIsotropicSpacing(isotropic); emitSettingChanged(); } + +void KisPressureSpacingOptionWidget::setUseSpacingUpdates(int useSpacingUpdates) +{ + dynamic_cast(KisCurveOptionWidget::curveOption())->setUsingSpacingUpdates(useSpacingUpdates); + emitSettingChanged(); +} diff --git a/plugins/paintops/libpaintop/kis_pressure_spacing_option_widget.h b/plugins/paintops/libpaintop/kis_pressure_spacing_option_widget.h index f3be68795c..bb5fd63dae 100644 --- a/plugins/paintops/libpaintop/kis_pressure_spacing_option_widget.h +++ b/plugins/paintops/libpaintop/kis_pressure_spacing_option_widget.h @@ -1,38 +1,39 @@ /* * Copyright (c) 2010 Lukáš Tvrdý * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KIS_PRESSURE_SPACING_OPTION_WIDGET_H #define KIS_PRESSURE_SPACING_OPTION_WIDGET_H #include "kis_curve_option_widget.h" class PAINTOP_EXPORT KisPressureSpacingOptionWidget : public KisCurveOptionWidget { Q_OBJECT public: KisPressureSpacingOptionWidget(); private Q_SLOTS: void setIsotropicSpacing(int isotropic); + void setUseSpacingUpdates(int useSpacingUpdates); }; #endif // KIS_PRESSURE_SPACING_OPTION_WIDGET_H diff --git a/plugins/paintops/particle/kis_particle_paintop.cpp b/plugins/paintops/particle/kis_particle_paintop.cpp index dc0e17c310..54d9b233f2 100644 --- a/plugins/paintops/particle/kis_particle_paintop.cpp +++ b/plugins/paintops/particle/kis_particle_paintop.cpp @@ -1,118 +1,123 @@ /* * Copyright (c) 2010 Lukáš Tvrdý * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_particle_paintop.h" #include "kis_particle_paintop_settings.h" #include #include "kis_vec.h" #include #include #include #include #include #include #include #include #include #include #include #include "kis_particleop_option.h" #include "particle_brush.h" KisParticlePaintOp::KisParticlePaintOp(const KisPaintOpSettingsSP settings, KisPainter * painter, KisNodeSP node, KisImageSP image) : KisPaintOp(painter) { Q_UNUSED(image); Q_UNUSED(node); m_properties.particleCount = settings->getInt(PARTICLE_COUNT); m_properties.iterations = settings->getInt(PARTICLE_ITERATIONS); m_properties.gravity = settings->getDouble(PARTICLE_GRAVITY); m_properties.weight = settings->getDouble(PARTICLE_WEIGHT); m_properties.scale = QPointF(settings->getDouble(PARTICLE_SCALE_X), settings->getDouble(PARTICLE_SCALE_Y)); m_particleBrush.setProperties(&m_properties); m_particleBrush.initParticles(); m_airbrushOption.readOptionSetting(settings); m_rateOption.readOptionSetting(settings); m_rateOption.resetAllSensors(); m_first = true; } KisParticlePaintOp::~KisParticlePaintOp() { } KisSpacingInformation KisParticlePaintOp::paintAt(const KisPaintInformation& info) { doPaintLine(info, info); return updateSpacingImpl(info); } KisSpacingInformation KisParticlePaintOp::updateSpacingImpl(const KisPaintInformation &info) const { return KisPaintOpPluginUtils::effectiveSpacing(0.0, 0.0, true, 0.0, false, 0.0, false, 0.0, KisLodTransform::lodToScale(painter()->device()), - &m_airbrushOption, nullptr, &m_rateOption, info); + &m_airbrushOption, nullptr, info); +} + +KisTimingInformation KisParticlePaintOp::updateTimingImpl(const KisPaintInformation &info) const +{ + return KisPaintOpPluginUtils::effectiveTiming(&m_airbrushOption, &m_rateOption, info); } void KisParticlePaintOp::paintLine(const KisPaintInformation &pi1, const KisPaintInformation &pi2, KisDistanceInformation *currentDistance) { // Use superclass behavior for lines of zero length. Otherwise, airbrushing can happen faster // than it is supposed to. if (pi1.pos() == pi2.pos()) { KisPaintOp::paintLine(pi1, pi2, currentDistance); } else { doPaintLine(pi1, pi2); } } void KisParticlePaintOp::doPaintLine(const KisPaintInformation &pi1, const KisPaintInformation &pi2) { if (!painter()) return; if (!m_dab) { m_dab = source()->createCompositionSourceDevice(); } else { m_dab->clear(); } if (m_first) { m_particleBrush.setInitialPosition(pi1.pos()); m_first = false; } m_particleBrush.draw(m_dab, painter()->paintColor(), pi2.pos()); QRect rc = m_dab->extent(); painter()->bitBlt(rc.x(), rc.y(), m_dab, rc.x(), rc.y(), rc.width(), rc.height()); painter()->renderMirrorMask(rc, m_dab); } diff --git a/plugins/paintops/particle/kis_particle_paintop.h b/plugins/paintops/particle/kis_particle_paintop.h index 571d7aa197..3b0c61205d 100644 --- a/plugins/paintops/particle/kis_particle_paintop.h +++ b/plugins/paintops/particle/kis_particle_paintop.h @@ -1,60 +1,62 @@ /* * Copyright (c) 2010 Lukáš Tvrdý * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_PARTICLE_PAINTOP_H_ #define KIS_PARTICLE_PAINTOP_H_ #include #include #include #include #include "kis_particle_paintop_settings.h" #include "particle_brush.h" class KisPainter; class KisPaintInformation; class KisParticlePaintOp : public KisPaintOp { public: KisParticlePaintOp(const KisPaintOpSettingsSP settings, KisPainter * painter, KisNodeSP node, KisImageSP image); ~KisParticlePaintOp() override; void paintLine(const KisPaintInformation &pi1, const KisPaintInformation &pi2, KisDistanceInformation *currentDistance) override; protected: KisSpacingInformation paintAt(const KisPaintInformation& info) override; KisSpacingInformation updateSpacingImpl(const KisPaintInformation &info) const override; + KisTimingInformation updateTimingImpl(const KisPaintInformation &info) const override; + private: void doPaintLine(const KisPaintInformation &pi1, const KisPaintInformation &pi2); private: KisParticleBrushProperties m_properties; KisPaintDeviceSP m_dab; ParticleBrush m_particleBrush; KisAirbrushOption m_airbrushOption; KisPressureRateOption m_rateOption; bool m_first; }; #endif // KIS_PARTICLE_PAINTOP_H_ diff --git a/plugins/paintops/roundmarker/kis_roundmarkerop.cpp b/plugins/paintops/roundmarker/kis_roundmarkerop.cpp index 6fc6c39580..6f23e5b0d1 100644 --- a/plugins/paintops/roundmarker/kis_roundmarkerop.cpp +++ b/plugins/paintops/roundmarker/kis_roundmarkerop.cpp @@ -1,164 +1,165 @@ /* * Copyright (c) 2016 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_roundmarkerop.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include +#include #include "kis_marker_painter.h" #include "kis_paintop_utils.h" KisRoundMarkerOp::KisRoundMarkerOp(KisPaintOpSettingsSP settings, KisPainter* painter, KisNodeSP node, KisImageSP image) : KisPaintOp(painter) , m_firstRun(true) , m_image(image) , m_lastRadius(1.0) { Q_UNUSED(node); Q_ASSERT(settings); Q_ASSERT(painter); m_markerOption.readOptionSetting(*settings); m_sizeOption.readOptionSetting(settings); m_spacingOption.readOptionSetting(settings); m_sizeOption.resetAllSensors(); m_spacingOption.resetAllSensors(); } KisRoundMarkerOp::~KisRoundMarkerOp() { } KisSpacingInformation KisRoundMarkerOp::paintAt(const KisPaintInformation& info) { // Simple error catching if (!painter()->device()) { return KisSpacingInformation(1.0); } // get the scaling factor calculated by the size option const qreal lodScale = KisLodTransform::lodToScale(painter()->device()); const qreal scale = m_sizeOption.apply(info) * lodScale; const qreal rotation = 0; // TODO const qreal diameter = m_markerOption.diameter * scale; qreal radius = 0.5 * diameter; if (KisPaintOpUtils::checkSizeTooSmall(scale, diameter, diameter)) return KisSpacingInformation(); KisDabShape shape(scale, 1.0, rotation); QPointF pos = info.pos(); KisMarkerPainter gc(painter()->device(), painter()->paintColor()); if (m_firstRun) { gc.fillFullCircle(pos, radius); } else { gc.fillCirclesDiff(m_lastPaintPos, m_lastRadius, pos, radius); } m_firstRun = false; m_lastPaintPos = pos; m_lastRadius = radius; QRectF dirtyRect(pos.x() - radius, pos.y() - radius, 2 * radius, 2 * radius); dirtyRect = kisGrowRect(dirtyRect, 1); painter()->addDirtyRect(dirtyRect.toAlignedRect()); // QPointF scatteredPos = // m_scatterOption.apply(info, // brush->maskWidth(shape, 0, 0, info), // brush->maskHeight(shape, 0, 0, info)); //updateMask(info, scale, rotation, scatteredPos); //QPointF newCenterPos = QRectF(m_dstDabRect).center(); /** * Save the center of the current dab to know where to read the * data during the next pass. We do not save scatteredPos here, * because it may differ slightly from the real center of the * brush (due to rounding effects), which will result in a * really weird quality. */ //QRect srcDabRect = m_dstDabRect.translated((m_lastPaintPos - newCenterPos).toPoint()); //m_lastPaintPos = newCenterPos; KisSpacingInformation spacingInfo = computeSpacing(info, diameter); if (m_firstRun) { m_firstRun = false; return spacingInfo; } return spacingInfo; } KisSpacingInformation KisRoundMarkerOp::updateSpacingImpl(const KisPaintInformation &info) const { const qreal lodScale = KisLodTransform::lodToScale(painter()->device()); const qreal diameter = m_markerOption.diameter * m_sizeOption.apply(info) * lodScale; return computeSpacing(info, diameter); } KisSpacingInformation KisRoundMarkerOp::computeSpacing(const KisPaintInformation &info, qreal diameter) const { const qreal rotation = 0; // TODO const bool axesFlipped = false; // TODO qreal extraSpacingScale = 1.0; if (m_spacingOption.isChecked()) { extraSpacingScale = m_spacingOption.apply(info); } return KisPaintOpUtils::effectiveSpacing(diameter, diameter, - extraSpacingScale, 1.0, true, true, rotation, - axesFlipped, m_markerOption.spacing, + extraSpacingScale, true, true, rotation, axesFlipped, + m_markerOption.spacing, m_markerOption.use_auto_spacing, - m_markerOption.auto_spacing_coeff, false, 0.0, + m_markerOption.auto_spacing_coeff, KisLodTransform::lodToScale(painter()->device())); } diff --git a/plugins/paintops/sketch/kis_sketch_paintop.cpp b/plugins/paintops/sketch/kis_sketch_paintop.cpp index 7762cf457c..26461d555b 100644 --- a/plugins/paintops/sketch/kis_sketch_paintop.cpp +++ b/plugins/paintops/sketch/kis_sketch_paintop.cpp @@ -1,320 +1,325 @@ /* * Copyright (c) 2010 Lukáš Tvrdý * Copyright (c) 2010 Ricardo Cabello * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_sketch_paintop.h" #include "kis_sketch_paintop_settings.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_lod_transform.h" #include /* * Based on Harmony project http://github.com/mrdoob/harmony/ */ // chrome : diff 0.2, sketchy : 0.3, fur: 0.5 // fur : distance / thresholdDistance // shaded: opacity per line :/ // ((1 - (d / 1000)) * 0.1 * BRUSH_PRESSURE), offset == 0 // chrome: color per line :/ //this.context.strokeStyle = "rgba(" + Math.floor(Math.random() * COLOR[0]) + ", " + Math.floor(Math.random() * COLOR[1]) + ", " + Math.floor(Math.random() * COLOR[2]) + ", " + 0.1 * BRUSH_PRESSURE + " )"; // long fur // from: count + offset * -random // to: i point - (offset * -random) + random * 2 // probability distance / thresholdDistnace // shaded: probabity : paint always - 0.0 density KisSketchPaintOp::KisSketchPaintOp(const KisPaintOpSettingsSP settings, KisPainter *painter, KisNodeSP node, KisImageSP image) : KisPaintOp(painter) { Q_UNUSED(image); Q_UNUSED(node); m_airbrushOption.readOptionSetting(settings); m_opacityOption.readOptionSetting(settings); m_sizeOption.readOptionSetting(settings); m_rotationOption.readOptionSetting(settings); m_rateOption.readOptionSetting(settings); m_sketchProperties.readOptionSetting(settings); m_brushOption.readOptionSettingForceCopy(settings); m_densityOption.readOptionSetting(settings); m_lineWidthOption.readOptionSetting(settings); m_offsetScaleOption.readOptionSetting(settings); m_brush = m_brushOption.brush(); m_dabCache = new KisDabCache(m_brush); m_opacityOption.resetAllSensors(); m_sizeOption.resetAllSensors(); m_rotationOption.resetAllSensors(); m_rateOption.resetAllSensors(); m_painter = 0; m_count = 0; } KisSketchPaintOp::~KisSketchPaintOp() { delete m_painter; delete m_dabCache; } void KisSketchPaintOp::drawConnection(const QPointF& start, const QPointF& end, double lineWidth) { if (lineWidth == 1.0) { m_painter->drawThickLine(start, end, lineWidth, lineWidth); } else { m_painter->drawLine(start, end, lineWidth, true); } } void KisSketchPaintOp::updateBrushMask(const KisPaintInformation& info, qreal scale, qreal rotation) { QRect dstRect; m_maskDab = m_dabCache->fetchDab(m_dab->colorSpace(), painter()->paintColor(), info.pos(), KisDabShape(scale, 1.0, rotation), info, 1.0, &dstRect); m_brushBoundingBox = dstRect; m_hotSpot = QPointF(0.5 * m_brushBoundingBox.width(), 0.5 * m_brushBoundingBox.height()); } void KisSketchPaintOp::paintLine(const KisPaintInformation &pi1, const KisPaintInformation &pi2, KisDistanceInformation *currentDistance) { // Use superclass behavior for lines of zero length. Otherwise, airbrushing can happen faster // than it is supposed to. if (pi1.pos() == pi2.pos()) { KisPaintOp::paintLine(pi1, pi2, currentDistance); } else { doPaintLine(pi1, pi2); } } void KisSketchPaintOp::doPaintLine(const KisPaintInformation &pi1, const KisPaintInformation &pi2) { if (!m_brush || !painter()) return; if (!m_dab) { m_dab = source()->createCompositionSourceDevice(); m_painter = new KisPainter(m_dab); m_painter->setPaintColor(painter()->paintColor()); } else { m_dab->clear(); } QPointF prevMouse = pi1.pos(); QPointF mousePosition = pi2.pos(); m_points.append(mousePosition); const qreal lodAdditionalScale = KisLodTransform::lodToScale(painter()->device()); const qreal scale = lodAdditionalScale * m_sizeOption.apply(pi2); if ((scale * m_brush->width()) <= 0.01 || (scale * m_brush->height()) <= 0.01) return; const qreal currentLineWidth = qMax(0.9, lodAdditionalScale * m_lineWidthOption.apply(pi2, m_sketchProperties.lineWidth)); const qreal currentOffsetScale = m_offsetScaleOption.apply(pi2, m_sketchProperties.offset); const double rotation = m_rotationOption.apply(pi2); const double currentProbability = m_densityOption.apply(pi2, m_sketchProperties.probability); // shaded: does not draw this line, chrome does, fur does if (m_sketchProperties.makeConnection) { drawConnection(prevMouse, mousePosition, currentLineWidth); } qreal thresholdDistance = 0.0; // update the mask for simple mode only once // determine the radius if (m_count == 0 && m_sketchProperties.simpleMode) { updateBrushMask(pi2, 1.0, 0.0); //m_radius = qMax(m_maskDab->bounds().width(),m_maskDab->bounds().height()) * 0.5; m_radius = 0.5 * qMax(m_brush->width(), m_brush->height()); } if (!m_sketchProperties.simpleMode) { updateBrushMask(pi2, scale, rotation); m_radius = qMax(m_maskDab->bounds().width(), m_maskDab->bounds().height()) * 0.5; thresholdDistance = pow(m_radius, 2); } if (m_sketchProperties.simpleMode) { // update the radius according scale in simple mode thresholdDistance = pow(m_radius * scale, 2); } // determine density const qreal density = thresholdDistance * currentProbability; // probability behaviour qreal probability = 1.0 - currentProbability; QColor painterColor = painter()->paintColor().toQColor(); QColor randomColor; KoColor color(m_dab->colorSpace()); int w = m_maskDab->bounds().width(); quint8 opacityU8 = 0; quint8 * pixel; qreal distance; QPoint positionInMask; QPointF diff; int size = m_points.size(); // MAIN LOOP for (int i = 0; i < size; i++) { diff = m_points.at(i) - mousePosition; distance = diff.x() * diff.x() + diff.y() * diff.y(); // circle test bool makeConnection = false; if (m_sketchProperties.simpleMode) { if (distance < thresholdDistance) { makeConnection = true; } // mask test } else { if (m_brushBoundingBox.contains(m_points.at(i))) { positionInMask = (diff + m_hotSpot).toPoint(); uint pos = ((positionInMask.y() * w + positionInMask.x()) * m_maskDab->pixelSize()); if (pos < m_maskDab->allocatedPixels() * m_maskDab->pixelSize()) { pixel = m_maskDab->data() + pos; opacityU8 = m_maskDab->colorSpace()->opacityU8(pixel); if (opacityU8 != 0) { makeConnection = true; } } } } if (!makeConnection) { // check next point continue; } if (m_sketchProperties.distanceDensity) { probability = distance / density; } KisRandomSourceSP randomSource = pi2.randomSource(); // density check if (randomSource->generateNormalized() >= probability) { QPointF offsetPt = diff * currentOffsetScale; if (m_sketchProperties.randomRGB) { /** * Since the order of calculation of function * parameters is not defined by C++ standard, we * should generate values in an external code snippet * which has a definite order of execution. */ qreal r1 = randomSource->generateNormalized(); qreal r2 = randomSource->generateNormalized(); qreal r3 = randomSource->generateNormalized(); // some color transformation per line goes here randomColor.setRgbF(r1 * painterColor.redF(), r2 * painterColor.greenF(), r3 * painterColor.blueF()); color.fromQColor(randomColor); m_painter->setPaintColor(color); } // distance based opacity quint8 opacity = OPACITY_OPAQUE_U8; if (m_sketchProperties.distanceOpacity) { opacity *= qRound((1.0 - (distance / thresholdDistance))); } if (m_sketchProperties.randomOpacity) { opacity *= randomSource->generateNormalized(); } m_painter->setOpacity(opacity); if (m_sketchProperties.magnetify) { drawConnection(mousePosition + offsetPt, m_points.at(i) - offsetPt, currentLineWidth); } else { drawConnection(mousePosition + offsetPt, mousePosition - offsetPt, currentLineWidth); } } }// end of MAIN LOOP m_count++; QRect rc = m_dab->extent(); quint8 origOpacity = m_opacityOption.apply(painter(), pi2); painter()->bitBlt(rc.x(), rc.y(), m_dab, rc.x(), rc.y(), rc.width(), rc.height()); painter()->renderMirrorMask(rc, m_dab); painter()->setOpacity(origOpacity); } KisSpacingInformation KisSketchPaintOp::paintAt(const KisPaintInformation& info) { doPaintLine(info, info); return updateSpacingImpl(info); } KisSpacingInformation KisSketchPaintOp::updateSpacingImpl(const KisPaintInformation &info) const { return KisPaintOpPluginUtils::effectiveSpacing(0.0, 0.0, true, 0.0, false, 0.0, false, 0.0, KisLodTransform::lodToScale(painter()->device()), - &m_airbrushOption, nullptr, &m_rateOption, info); + &m_airbrushOption, nullptr, info); +} + +KisTimingInformation KisSketchPaintOp::updateTimingImpl(const KisPaintInformation &info) const +{ + return KisPaintOpPluginUtils::effectiveTiming(&m_airbrushOption, &m_rateOption, info); } diff --git a/plugins/paintops/sketch/kis_sketch_paintop.h b/plugins/paintops/sketch/kis_sketch_paintop.h index a52bd8417b..c41e321a1f 100644 --- a/plugins/paintops/sketch/kis_sketch_paintop.h +++ b/plugins/paintops/sketch/kis_sketch_paintop.h @@ -1,92 +1,94 @@ /* * Copyright (c) 2010 Lukáš Tvrdý * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_SKETCH_PAINTOP_H_ #define KIS_SKETCH_PAINTOP_H_ #include #include #include "kis_density_option.h" #include "kis_sketchop_option.h" #include "kis_sketch_paintop_settings.h" #include "kis_painter.h" #include #include #include #include #include #include "kis_linewidth_option.h" #include "kis_offset_scale_option.h" class KisDabCache; class KisSketchPaintOp : public KisPaintOp { public: KisSketchPaintOp(const KisPaintOpSettingsSP settings, KisPainter *painter, KisNodeSP node, KisImageSP image); ~KisSketchPaintOp() override; void paintLine(const KisPaintInformation &pi1, const KisPaintInformation &pi2, KisDistanceInformation *currentDistance) override; protected: KisSpacingInformation paintAt(const KisPaintInformation& info) override; KisSpacingInformation updateSpacingImpl(const KisPaintInformation &info) const override; + KisTimingInformation updateTimingImpl(const KisPaintInformation &info) const override; + private: // pixel buffer KisPaintDeviceSP m_dab; // mask detection area KisFixedPaintDeviceSP m_maskDab; QRectF m_brushBoundingBox; QPointF m_hotSpot; // simple mode qreal m_radius; KisPressureOpacityOption m_opacityOption; KisPressureSizeOption m_sizeOption; KisPressureRotationOption m_rotationOption; KisPressureRateOption m_rateOption; KisDensityOption m_densityOption; KisLineWidthOption m_lineWidthOption; KisOffsetScaleOption m_offsetScaleOption; KisAirbrushOption m_airbrushOption; KisBrushOption m_brushOption; SketchProperties m_sketchProperties; QVector m_points; int m_count; KisPainter * m_painter; KisBrushSP m_brush; KisDabCache *m_dabCache; private: void drawConnection(const QPointF &start, const QPointF &end, double lineWidth); void updateBrushMask(const KisPaintInformation& info, qreal scale, qreal rotation); void doPaintLine(const KisPaintInformation &pi1, const KisPaintInformation &pi2); }; #endif // KIS_SKETCH_PAINTOP_H_ diff --git a/plugins/paintops/spray/kis_spray_paintop.cpp b/plugins/paintops/spray/kis_spray_paintop.cpp index 8c8e5c8fdb..6e3d353610 100644 --- a/plugins/paintops/spray/kis_spray_paintop.cpp +++ b/plugins/paintops/spray/kis_spray_paintop.cpp @@ -1,148 +1,153 @@ /* * Copyright (c) 2008-2012 Lukáš Tvrdý * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_spray_paintop.h" #include "kis_spray_paintop_settings.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include KisSprayPaintOp::KisSprayPaintOp(const KisPaintOpSettingsSP settings, KisPainter *painter, KisNodeSP node, KisImageSP image) : KisPaintOp(painter) , m_isPresetValid(true) , m_node(node) { Q_ASSERT(settings); Q_ASSERT(painter); Q_UNUSED(image); m_airbrushOption.readOptionSetting(settings); m_rotationOption.readOptionSetting(settings); m_opacityOption.readOptionSetting(settings); m_sizeOption.readOptionSetting(settings); m_rateOption.readOptionSetting(settings); m_rotationOption.resetAllSensors(); m_opacityOption.resetAllSensors(); m_sizeOption.resetAllSensors(); m_rateOption.resetAllSensors(); m_brushOption.readOptionSettingForceCopy(settings); m_colorProperties.fillProperties(settings); m_properties.readOptionSetting(settings); // first load tip properties as shape properties are dependent on diameter/scale/aspect m_shapeProperties.loadSettings(settings, m_properties.diameter * m_properties.scale, m_properties.diameter * m_properties.aspect * m_properties.scale); // TODO: what to do with proportional sizes? m_shapeDynamicsProperties.loadSettings(settings); if (!m_shapeProperties.enabled && !m_brushOption.brush()) { // in case the preset does not contain the definition for KisBrush m_isPresetValid = false; dbgKrita << "Preset is not valid. Painting is not possible. Use the preset editor to fix current brush engine preset."; } m_sprayBrush.setProperties(&m_properties, &m_colorProperties, &m_shapeProperties, &m_shapeDynamicsProperties, m_brushOption.brush()); m_sprayBrush.setFixedDab(cachedDab()); // spacing if ((m_properties.diameter * 0.5) > 1) { m_ySpacing = m_xSpacing = m_properties.diameter * 0.5 * m_properties.spacing; } else { m_ySpacing = m_xSpacing = 1.0; } m_spacing = m_xSpacing; } KisSprayPaintOp::~KisSprayPaintOp() { } KisSpacingInformation KisSprayPaintOp::paintAt(const KisPaintInformation& info) { if (!painter() || !m_isPresetValid) { return KisSpacingInformation(m_spacing); } if (!m_dab) { m_dab = source()->createCompositionSourceDevice(); } else { m_dab->clear(); } qreal rotation = m_rotationOption.apply(info); quint8 origOpacity = m_opacityOption.apply(painter(), info); // Spray Brush is capable of working with zero scale, // so no additional checks for 'zero'ness are needed const qreal scale = m_sizeOption.apply(info); const qreal lodScale = KisLodTransform::lodToScale(painter()->device()); m_sprayBrush.paint(m_dab, m_node->paintDevice(), info, rotation, scale, lodScale, painter()->paintColor(), painter()->backgroundColor()); QRect rc = m_dab->extent(); painter()->bitBlt(rc.topLeft(), m_dab, rc); painter()->renderMirrorMask(rc, m_dab); painter()->setOpacity(origOpacity); return computeSpacing(info, lodScale); } KisSpacingInformation KisSprayPaintOp::updateSpacingImpl(const KisPaintInformation &info) const { return computeSpacing(info, KisLodTransform::lodToScale(painter()->device())); } +KisTimingInformation KisSprayPaintOp::updateTimingImpl(const KisPaintInformation &info) const +{ + return KisPaintOpPluginUtils::effectiveTiming(&m_airbrushOption, &m_rateOption, info); +} + KisSpacingInformation KisSprayPaintOp::computeSpacing(const KisPaintInformation &info, qreal lodScale) const { return KisPaintOpPluginUtils::effectiveSpacing(1.0, 1.0, true, 0.0, false, m_spacing * lodScale, false, 1.0, lodScale, - &m_airbrushOption, nullptr, &m_rateOption, info); + &m_airbrushOption, nullptr, info); } diff --git a/plugins/paintops/spray/kis_spray_paintop.h b/plugins/paintops/spray/kis_spray_paintop.h index 168418a8eb..2e5c316e6c 100644 --- a/plugins/paintops/spray/kis_spray_paintop.h +++ b/plugins/paintops/spray/kis_spray_paintop.h @@ -1,73 +1,75 @@ /* * Copyright (c) 2008-2012 Lukáš Tvrdý * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_SPRAY_PAINTOP_H_ #define KIS_SPRAY_PAINTOP_H_ #include #include #include "spray_brush.h" #include "kis_spray_paintop_settings.h" #include "kis_brush_option.h" #include #include #include #include #include class KisPainter; class KisSprayPaintOp : public KisPaintOp { public: KisSprayPaintOp(const KisPaintOpSettingsSP settings, KisPainter * painter, KisNodeSP node, KisImageSP image); ~KisSprayPaintOp() override; protected: KisSpacingInformation paintAt(const KisPaintInformation& info) override; KisSpacingInformation updateSpacingImpl(const KisPaintInformation &info) const override; + KisTimingInformation updateTimingImpl(const KisPaintInformation &info) const override; + private: KisSpacingInformation computeSpacing(const KisPaintInformation &info, qreal lodScale) const; private: KisShapeProperties m_shapeProperties; KisSprayProperties m_properties; KisShapeDynamicsProperties m_shapeDynamicsProperties; KisColorProperties m_colorProperties; KisBrushOption m_brushOption; KisPaintDeviceSP m_dab; SprayBrush m_sprayBrush; qreal m_xSpacing, m_ySpacing, m_spacing; bool m_isPresetValid; KisAirbrushOption m_airbrushOption; KisPressureRotationOption m_rotationOption; KisPressureSizeOption m_sizeOption; KisPressureOpacityOption m_opacityOption; KisPressureRateOption m_rateOption; KisNodeSP m_node; }; #endif // KIS_SPRAY_PAINTOP_H_ diff --git a/plugins/paintops/tangentnormal/kis_tangent_normal_paintop.cpp b/plugins/paintops/tangentnormal/kis_tangent_normal_paintop.cpp index a355ea6a7e..aaf810a618 100644 --- a/plugins/paintops/tangentnormal/kis_tangent_normal_paintop.cpp +++ b/plugins/paintops/tangentnormal/kis_tangent_normal_paintop.cpp @@ -1,248 +1,253 @@ /* * Copyright (C) 2015 Wolthera van Hövell tot Westerflier * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_tangent_normal_paintop.h" #include #include #include #include #include #include #include #include #include #include #include +#include KisTangentNormalPaintOp::KisTangentNormalPaintOp(const KisPaintOpSettingsSP settings, KisPainter* painter, KisNodeSP node, KisImageSP image): KisBrushBasedPaintOp(settings, painter), m_opacityOption(node), m_tempDev(painter->device()->createCompositionSourceDevice()) { Q_UNUSED(image); //Init, read settings, etc// m_tangentTiltOption.readOptionSetting(settings); m_airbrushOption.readOptionSetting(settings); m_sizeOption.readOptionSetting(settings); m_opacityOption.readOptionSetting(settings); m_flowOption.readOptionSetting(settings); m_spacingOption.readOptionSetting(settings); m_rateOption.readOptionSetting(settings); m_softnessOption.readOptionSetting(settings); m_sharpnessOption.readOptionSetting(settings); m_rotationOption.readOptionSetting(settings); m_scatterOption.readOptionSetting(settings); m_sizeOption.resetAllSensors(); m_opacityOption.resetAllSensors(); m_flowOption.resetAllSensors(); m_spacingOption.resetAllSensors(); m_rateOption.resetAllSensors(); m_softnessOption.resetAllSensors(); m_sharpnessOption.resetAllSensors(); m_rotationOption.resetAllSensors(); m_scatterOption.resetAllSensors(); m_dabCache->setSharpnessPostprocessing(&m_sharpnessOption); m_rotationOption.applyFanCornersInfo(this); } KisTangentNormalPaintOp::~KisTangentNormalPaintOp() { //destroy things here// } KisSpacingInformation KisTangentNormalPaintOp::paintAt(const KisPaintInformation& info) { /* * For the color, the precision of tilt is only 60x60, and the precision of direction and rotation are 360 and 360*90. * You can't get more precise than 8bit. Therefore, we will check if the current space is RGB, * if so we request a profile with that space and 8bit bit depth, if not, just sRGB */ KoColor currentColor = painter()->paintColor(); QString currentSpace = currentColor.colorSpace()->colorModelId().id(); const KoColorSpace* rgbColorSpace = KoColorSpaceRegistry::instance()->rgb8(); if (currentSpace != "RGBA") { rgbColorSpace = KoColorSpaceRegistry::instance()->rgb8(); } else { rgbColorSpace = currentColor.colorSpace(); } QVector channelValues(4); qreal r, g, b; if (currentColor.colorSpace()->colorDepthId().id()=="F16" || currentColor.colorSpace()->colorDepthId().id()=="F32"){ channelValues[0] = 0.5;//red channelValues[1] = 0.5;//green channelValues[2] = 1.0;//blue channelValues[3] = 1.0;//alpha, leave alone. m_tangentTiltOption.apply(info, &r, &g, &b); channelValues[0] = r;//red channelValues[1] = g;//green channelValues[2] = b;//blue } else { channelValues[0] = 1.0;//blue channelValues[1] = 0.5;//green channelValues[2] = 0.5;//red channelValues[3] = 1.0;//alpha, leave alone. m_tangentTiltOption.apply(info, &r, &g, &b); channelValues[0] = b;//blue channelValues[1] = g;//green channelValues[2] = r;//red } quint8 data[4]; rgbColorSpace->fromNormalisedChannelsValue(data, channelValues); KoColor color(data, rgbColorSpace);//Should be default RGB(0.5,0.5,1.0) //draw stuff here, return kisspacinginformation. KisBrushSP brush = m_brush; if (!painter()->device() || !brush || !brush->canPaintFor(info)) { return KisSpacingInformation(1.0); } qreal scale = m_sizeOption.apply(info); scale *= KisLodTransform::lodToScale(painter()->device()); qreal rotation = m_rotationOption.apply(info); if (checkSizeTooSmall(scale)) return KisSpacingInformation(); KisDabShape shape(scale, 1.0, rotation); QPointF cursorPos = m_scatterOption.apply(info, brush->maskWidth(shape, 0, 0, info), brush->maskHeight(shape, 0, 0, info)); m_maskDab = m_dabCache->fetchDab(rgbColorSpace, color, cursorPos, shape, info, m_softnessOption.apply(info), &m_dstDabRect); if (m_dstDabRect.isEmpty()) return KisSpacingInformation(1.0); QRect dabRect = m_maskDab->bounds(); // sanity check Q_ASSERT(m_dstDabRect.size() == dabRect.size()); Q_UNUSED(dabRect); quint8 oldOpacity = painter()->opacity(); QString oldCompositeOpId = painter()->compositeOp()->id(); m_opacityOption.setFlow(m_flowOption.apply(info)); m_opacityOption.apply(painter(), info); //paint with the default color? Copied this from color smudge.// //painter()->setCompositeOp(COMPOSITE_COPY); //painter()->fill(0, 0, m_dstDabRect.width(), m_dstDabRect.height(), color); painter()->bltFixed(m_dstDabRect.topLeft(), m_maskDab, m_maskDab->bounds()); painter()->renderMirrorMaskSafe(m_dstDabRect, m_maskDab, !m_dabCache->needSeparateOriginal()); // restore orginal opacity and composite mode values painter()->setOpacity(oldOpacity); painter()->setCompositeOp(oldCompositeOpId); return computeSpacing(info, scale, rotation); } KisSpacingInformation KisTangentNormalPaintOp::updateSpacingImpl(const KisPaintInformation &info) const { qreal scale = m_sizeOption.apply(info) * KisLodTransform::lodToScale(painter()->device()); qreal rotation = m_rotationOption.apply(info); return computeSpacing(info, scale, rotation); } +KisTimingInformation KisTangentNormalPaintOp::updateTimingImpl(const KisPaintInformation &info) const +{ + return KisPaintOpPluginUtils::effectiveTiming(&m_airbrushOption, &m_rateOption, info); +} + KisSpacingInformation KisTangentNormalPaintOp::computeSpacing(const KisPaintInformation &info, qreal scale, qreal rotation) const { - return effectiveSpacing(scale, rotation, &m_airbrushOption, &m_spacingOption, &m_rateOption, - info); + return effectiveSpacing(scale, rotation, &m_airbrushOption, &m_spacingOption, info); } void KisTangentNormalPaintOp::paintLine(const KisPaintInformation& pi1, const KisPaintInformation& pi2, KisDistanceInformation *currentDistance) { if (m_sharpnessOption.isChecked() && m_brush && (m_brush->width() == 1) && (m_brush->height() == 1)) { if (!m_lineCacheDevice) { m_lineCacheDevice = m_tempDev; } else { m_lineCacheDevice->clear(); } KisPainter p(m_lineCacheDevice); KoColor currentColor = painter()->paintColor(); QString currentSpace = currentColor.colorSpace()->colorModelId().id(); const KoColorSpace* rgbColorSpace = KoColorSpaceRegistry::instance()->rgb8(); if (currentSpace != "RGBA") { rgbColorSpace = KoColorSpaceRegistry::instance()->rgb8(); } else { rgbColorSpace = currentColor.colorSpace(); } QVector channelValues(4); qreal r, g, b; if (currentColor.colorSpace()->colorDepthId().id()=="F16" || currentColor.colorSpace()->colorDepthId().id()=="F32"){ channelValues[0] = 0.5;//red channelValues[1] = 0.5;//green channelValues[2] = 1.0;//blue channelValues[3] = 1.0;//alpha, leave alone. m_tangentTiltOption.apply(pi2, &r, &g, &b); channelValues[0] = r;//red channelValues[1] = g;//green channelValues[2] = b;//blue } else { channelValues[0] = 1.0;//blue channelValues[1] = 0.5;//green channelValues[2] = 0.5;//red channelValues[3] = 1.0;//alpha, leave alone. m_tangentTiltOption.apply(pi2, &r, &g, &b); channelValues[0] = b;//blue channelValues[1] = g;//green channelValues[2] = r;//red } quint8 data[4]; rgbColorSpace->fromNormalisedChannelsValue(data, channelValues); KoColor color(data, rgbColorSpace); p.setPaintColor(color); p.drawDDALine(pi1.pos(), pi2.pos()); QRect rc = m_lineCacheDevice->extent(); painter()->bitBlt(rc.x(), rc.y(), m_lineCacheDevice, rc.x(), rc.y(), rc.width(), rc.height()); painter()->renderMirrorMask(rc, m_lineCacheDevice); } else { KisPaintOp::paintLine(pi1, pi2, currentDistance); } } diff --git a/plugins/paintops/tangentnormal/kis_tangent_normal_paintop.h b/plugins/paintops/tangentnormal/kis_tangent_normal_paintop.h index 86bac2a741..97074af7c5 100644 --- a/plugins/paintops/tangentnormal/kis_tangent_normal_paintop.h +++ b/plugins/paintops/tangentnormal/kis_tangent_normal_paintop.h @@ -1,83 +1,85 @@ /* * Copyright (C) 2015 Wolthera van Hövell tot Westerflier * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef _KIS_TANGENTNORMALPAINTOP_H_ #define _KIS_TANGENTNORMALPAINTOP_H_ #include #include #include #include #include #include #include #include #include #include #include #include #include #include class KisBrushBasedPaintOpSettings; class KisPainter; class KisTangentNormalPaintOp: public KisBrushBasedPaintOp { public: //public functions// /* Create a Tangent Normal Brush Operator*/ KisTangentNormalPaintOp(const KisPaintOpSettingsSP settings, KisPainter* painter, KisNodeSP node, KisImageSP image); ~KisTangentNormalPaintOp() override; void paintLine(const KisPaintInformation &pi1, const KisPaintInformation &pi2, KisDistanceInformation *currentDistance) override; protected: /*paint the dabs*/ KisSpacingInformation paintAt(const KisPaintInformation& info) override; KisSpacingInformation updateSpacingImpl(const KisPaintInformation &info) const override; + KisTimingInformation updateTimingImpl(const KisPaintInformation &info) const override; + private: KisSpacingInformation computeSpacing(const KisPaintInformation &info, qreal scale, qreal rotation) const; private: //private functions// KisPressureSizeOption m_sizeOption; KisFlowOpacityOption m_opacityOption; KisPressureSpacingOption m_spacingOption; KisPressureRateOption m_rateOption; KisPressureRotationOption m_rotationOption; KisPressureScatterOption m_scatterOption; KisTangentTiltOption m_tangentTiltOption; KisAirbrushOption m_airbrushOption; KisPressureSoftnessOption m_softnessOption; KisPressureSharpnessOption m_sharpnessOption; KisPressureFlowOption m_flowOption; KisFixedPaintDeviceSP m_maskDab; KisPaintDeviceSP m_tempDev; QRect m_dstDabRect; KisPaintDeviceSP m_lineCacheDevice; }; #endif // _KIS_TANGENTNORMALPAINTOP_H_ diff --git a/plugins/tools/tool_transform2/kis_liquify_paintop.cpp b/plugins/tools/tool_transform2/kis_liquify_paintop.cpp index 0549086088..c53fee0727 100644 --- a/plugins/tools/tool_transform2/kis_liquify_paintop.cpp +++ b/plugins/tools/tool_transform2/kis_liquify_paintop.cpp @@ -1,192 +1,215 @@ /* * Copyright (c) 2014 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_liquify_paintop.h" #include #include #include #include "kis_liquify_transform_worker.h" #include "kis_algebra_2d.h" #include "kis_liquify_properties.h" +#include "kis_spacing_information.h" +#include "kis_timing_information.h" struct KisLiquifyPaintop::Private { Private(const KisLiquifyProperties &_props, KisLiquifyTransformWorker *_worker) : props(_props), worker(_worker) {} KisLiquifyProperties props; KisLiquifyTransformWorker *worker; }; KisLiquifyPaintop::KisLiquifyPaintop(const KisLiquifyProperties &props, KisLiquifyTransformWorker *worker) : m_d(new Private(props, worker)) { } KisLiquifyPaintop::~KisLiquifyPaintop() { } QPainterPath KisLiquifyPaintop::brushOutline(const KisLiquifyProperties &props, const KisPaintInformation &info) { const qreal diameter = props.size(); const qreal reverseCoeff = props.reverseDirection() ? -1.0 : 1.0; QPainterPath outline; outline.addEllipse(-0.5 * diameter, -0.5 * diameter, diameter, diameter); switch (props.mode()) { case KisLiquifyProperties::MOVE: case KisLiquifyProperties::SCALE: break; case KisLiquifyProperties::ROTATE: { QPainterPath p; p.lineTo(-3.0, 4.0); p.moveTo(0.0, 0.0); p.lineTo(-3.0, -4.0); QTransform S; if (diameter < 15.0) { const qreal scale = diameter / 15.0; S = QTransform::fromScale(scale, scale); } QTransform R; R.rotateRadians(-reverseCoeff * 0.5 * M_PI); QTransform T = QTransform::fromTranslate(0.5 * diameter, 0.0); p = (S * R * T).map(p); outline.addPath(p); break; } case KisLiquifyProperties::OFFSET: { qreal normalAngle = info.drawingAngle() + reverseCoeff * 0.5 * M_PI; QPainterPath p = KisAlgebra2D::smallArrow(); const qreal offset = qMax(0.8 * diameter, 15.0); QTransform R; R.rotateRadians(normalAngle); QTransform T = QTransform::fromTranslate(offset, 0.0); p = (T * R).map(p); outline.addPath(p); break; } case KisLiquifyProperties::UNDO: break; case KisLiquifyProperties::N_MODES: qFatal("Not supported mode"); } return outline; } // TODO: Reduce code duplication between KisLiquifyPaintop and KisPaintOp. It might be possible to // make them both subclasses of some more general base class. void KisLiquifyPaintop::updateSpacing(const KisPaintInformation &info, KisDistanceInformation ¤tDistance) const { KisPaintInformation pi(info); KisSpacingInformation spacingInfo; { KisPaintInformation::DistanceInformationRegistrar r = pi.registerDistanceInformation(¤tDistance); spacingInfo = updateSpacingImpl(pi); } - currentDistance.setSpacing(spacingInfo); + currentDistance.updateSpacing(spacingInfo); +} + +void KisLiquifyPaintop::updateTiming(const KisPaintInformation &info, + KisDistanceInformation ¤tDistance) const +{ + KisPaintInformation pi(info); + KisTimingInformation timingInfo; + { + KisPaintInformation::DistanceInformationRegistrar r + = pi.registerDistanceInformation(¤tDistance); + timingInfo = updateTimingImpl(pi); + } + + currentDistance.updateTiming(timingInfo); } KisSpacingInformation KisLiquifyPaintop::paintAt(const KisPaintInformation &pi) { const qreal size = computeSize(pi); const qreal spacing = m_d->props.spacing() * size; const qreal reverseCoeff = m_d->props.mode() != KisLiquifyProperties::UNDO && m_d->props.reverseDirection() ? -1.0 : 1.0; const qreal amount = m_d->props.amountHasPressure() ? pi.pressure() * reverseCoeff * m_d->props.amount(): reverseCoeff * m_d->props.amount(); const bool useWashMode = m_d->props.useWashMode(); const qreal flow = m_d->props.flow(); switch (m_d->props.mode()) { case KisLiquifyProperties::MOVE: { const qreal offsetLength = size * amount; m_d->worker->translatePoints(pi.pos(), pi.drawingDirectionVector() * offsetLength, size, useWashMode, flow); break; } case KisLiquifyProperties::SCALE: m_d->worker->scalePoints(pi.pos(), amount, size, useWashMode, flow); break; case KisLiquifyProperties::ROTATE: m_d->worker->rotatePoints(pi.pos(), 2.0 * M_PI * amount, size, useWashMode, flow); break; case KisLiquifyProperties::OFFSET: { const qreal offsetLength = size * amount; m_d->worker->translatePoints(pi.pos(), KisAlgebra2D::rightUnitNormal(pi.drawingDirectionVector()) * offsetLength, size, useWashMode, flow); break; } case KisLiquifyProperties::UNDO: m_d->worker->undoPoints(pi.pos(), amount, size); break; case KisLiquifyProperties::N_MODES: qFatal("Not supported mode"); } return KisSpacingInformation(spacing); } KisSpacingInformation KisLiquifyPaintop::updateSpacingImpl(const KisPaintInformation &pi) const { return KisSpacingInformation(m_d->props.spacing() * computeSize(pi)); } +KisTimingInformation KisLiquifyPaintop::updateTimingImpl(const KisPaintInformation &pi) const +{ + Q_UNUSED(pi); + // Don't use airbrushing. + return KisTimingInformation(); +} + qreal KisLiquifyPaintop::computeSize(const KisPaintInformation &pi) const { static const qreal sizeToSigmaCoeff = 1.0 / 3.0; return sizeToSigmaCoeff * (m_d->props.sizeHasPressure() ? pi.pressure() * m_d->props.size(): m_d->props.size()); } diff --git a/plugins/tools/tool_transform2/kis_liquify_paintop.h b/plugins/tools/tool_transform2/kis_liquify_paintop.h index dd48af2b91..fc8e059e62 100644 --- a/plugins/tools/tool_transform2/kis_liquify_paintop.h +++ b/plugins/tools/tool_transform2/kis_liquify_paintop.h @@ -1,60 +1,69 @@ /* * Copyright (c) 2014 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef __KIS_LIQUIFY_PAINTOP_H #define __KIS_LIQUIFY_PAINTOP_H #include class KisLiquifyTransformWorker; class KisPaintInformation; class KisSpacingInformation; +class KisTimingInformation; class KisDistanceInformation; class KisLiquifyProperties; class QPainterPath; class KisLiquifyPaintop { public: KisLiquifyPaintop(const KisLiquifyProperties &props, KisLiquifyTransformWorker *worker); ~KisLiquifyPaintop(); KisSpacingInformation paintAt(const KisPaintInformation &pi); /** * Updates the spacing in currentDistance based on the provided information. */ void updateSpacing(const KisPaintInformation &info, KisDistanceInformation ¤tDistance) const; + /** + * Updates the timing in currentDistance based on the provided information. + */ + void updateTiming(const KisPaintInformation &info, KisDistanceInformation ¤tDistance) + const; + + KisTimingInformation updateTimingImpl(const KisPaintInformation &pi) const; + static QPainterPath brushOutline(const KisLiquifyProperties &props, const KisPaintInformation &info); protected: KisSpacingInformation updateSpacingImpl(const KisPaintInformation &pi) const; private: qreal computeSize(const KisPaintInformation &pi) const; private: struct Private; const QScopedPointer m_d; }; #endif /* __KIS_LIQUIFY_PAINTOP_H */