1 /**
2 * UGENE - Integrated Bioinformatics Tools.
3 * Copyright (C) 2008-2021 UniPro <ugene@unipro.ru>
4 * http://ugene.net
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 * MA 02110-1301, USA.
20 */
21
22 #include "AssemblyCellRenderer.h"
23
24 #include <QFont>
25 #include <QPainter>
26
27 #include <U2Core/Timer.h>
28 #include <U2Core/U2SafePoints.h>
29
30 namespace U2 {
31
32 namespace {
33
34 typedef QPair<char, char> TwoChars;
35
initDefaultColorSheme()36 static QMap<char, QColor> initDefaultColorSheme() {
37 QMap<char, QColor> colors;
38
39 // TODO other chars ??
40 // TODO = symbol
41 colors['a'] = colors['A'] = QColor("#FCFF92");
42 colors['c'] = colors['C'] = QColor("#70F970");
43 colors['g'] = colors['G'] = QColor("#4EADE1");
44 colors['t'] = colors['T'] = QColor("#FF99B1");
45
46 // TODO: more meaningful colors
47 colors['w'] = colors['W'] =
48 colors['r'] = colors['R'] =
49 colors['m'] = colors['M'] =
50 colors['k'] = colors['K'] =
51 colors['y'] = colors['Y'] =
52 colors['s'] = colors['S'] =
53
54 colors['b'] = colors['B'] =
55 colors['v'] = colors['V'] =
56 colors['d'] = colors['D'] =
57 colors['h'] = colors['H'] = QColor("#FFAA60");
58
59 colors['-'] = QColor("#FBFBFB");
60 colors['N'] = QColor("#FBFBFB");
61
62 return colors;
63 }
64
initExtendedPairs()65 static QMap<char, TwoChars> initExtendedPairs() {
66 QMap<char, TwoChars> pairs;
67
68 pairs['w'] = pairs['W'] = TwoChars('T', 'A');
69 pairs['r'] = pairs['R'] = TwoChars('G', 'A');
70 pairs['m'] = pairs['M'] = TwoChars('C', 'A');
71 pairs['k'] = pairs['K'] = TwoChars('G', 'T');
72 pairs['y'] = pairs['Y'] = TwoChars('T', 'C');
73 pairs['s'] = pairs['S'] = TwoChars('G', 'C');
74 return pairs;
75 }
76
isGap(char c)77 inline static bool isGap(char c) {
78 // TODO : get rid of hardcoded values!
79 // TODO: smarter analysis. Don't forget about '=' symbol and IUPAC codes
80 return (c == '-' || c == 'N');
81 }
82
83 } // namespace
84
85 static const QMap<char, QColor> nucleotideColorScheme = initDefaultColorSheme();
86 static const QList<char> assemblyAlphabet = nucleotideColorScheme.keys();
87 static const QMap<char, TwoChars> extendedPairs = initExtendedPairs();
88
drawBackground(QPixmap & img,const QSize & size,const QColor & topColor,const QColor & bottomColor)89 static void drawBackground(QPixmap &img, const QSize &size, const QColor &topColor, const QColor &bottomColor) {
90 QPainter p(&img);
91
92 // TODO invent something greater
93 QLinearGradient linearGrad(QPointF(0, 0), QPointF(size.width(), size.height()));
94 linearGrad.setColorAt(0, QColor::fromRgb(topColor.red() - 70, topColor.green() - 70, topColor.blue() - 70));
95 linearGrad.setColorAt(1, bottomColor);
96 QBrush br(linearGrad);
97
98 QRect rect = QRect(QPoint(), size);
99 p.fillRect(rect, br);
100 }
101
drawText(QPixmap & img,const QSize & size,char c,const QFont & font,const QColor & color)102 static void drawText(QPixmap &img, const QSize &size, char c, const QFont &font, const QColor &color) {
103 QPainter p(&img);
104 p.setFont(font);
105 p.setPen(color);
106 QRect rect = QRect(QPoint(), size);
107 p.drawText(rect, Qt::AlignCenter, QString(c));
108 }
109
drawCell(QPixmap & img,const QSize & size,const QColor & topColor,const QColor & bottomColor,bool text,char c,const QFont & font,const QColor & textColor)110 void AssemblyCellRenderer::drawCell(QPixmap &img, const QSize &size, const QColor &topColor, const QColor &bottomColor, bool text, char c, const QFont &font, const QColor &textColor) {
111 drawBackground(img, size, topColor, bottomColor);
112
113 if (text) {
114 drawText(img, size, c, font, textColor);
115 }
116 }
117
118 class NucleotideColorsRenderer : public AssemblyCellRenderer {
119 public:
NucleotideColorsRenderer()120 NucleotideColorsRenderer()
121 : AssemblyCellRenderer(), colorScheme(nucleotideColorScheme),
122 images(), unknownChar(), size(), devicePixelRatio(0), text(false), font() {
123 }
~NucleotideColorsRenderer()124 virtual ~NucleotideColorsRenderer() {
125 }
126
127 virtual void render(const QSize &size, int devicePixelRatio, bool text, const QFont &font);
128
129 virtual QPixmap cellImage(char c);
130 virtual QPixmap cellImage(const U2AssemblyRead &read, char c);
131 virtual QPixmap cellImage(const U2AssemblyRead &read, char c, char ref);
132
133 private:
134 void update();
135
136 private:
137 QMap<char, QColor> colorScheme;
138
139 // images cache
140 QHash<char, QPixmap> images;
141 QPixmap unknownChar;
142
143 // cached cells parameters
144 QSize size;
145 int devicePixelRatio;
146 bool text;
147 QFont font;
148 };
149
render(const QSize & _size,int _devicePixelRatio,bool _text,const QFont & _font)150 void NucleotideColorsRenderer::render(const QSize &_size, int _devicePixelRatio, bool _text, const QFont &_font) {
151 GTIMER(c1, t1, "NucleotideColorsRenderer::render");
152
153 if (_size != size || _devicePixelRatio != devicePixelRatio || _text != text || (text && _font != font)) {
154 // update cache
155 size = _size, devicePixelRatio = _devicePixelRatio, text = _text, font = _font;
156 update();
157 }
158 }
159
update()160 void NucleotideColorsRenderer::update() {
161 images.clear();
162
163 foreach (char c, colorScheme.keys()) {
164 QPixmap img(size * devicePixelRatio);
165 img.setDevicePixelRatio(devicePixelRatio);
166 QColor textColor = isGap(c) ? Qt::red : Qt::black;
167 if (extendedPairs.contains(c)) {
168 // char from extended alphabet, draw gradient
169 TwoChars pair = extendedPairs.value(c);
170 drawCell(img, size, colorScheme.value(pair.first), colorScheme.value(pair.second), text, c, font, textColor);
171 } else {
172 // normal char
173 drawCell(img, size, colorScheme.value(c), text, c, font, textColor);
174 }
175 images.insert(c, img);
176 }
177
178 unknownChar = QPixmap(size * devicePixelRatio);
179 unknownChar.setDevicePixelRatio(devicePixelRatio);
180 drawCell(unknownChar, size, QColor("#FBFBFB"), text, '?', font, Qt::red);
181 }
182
cellImage(char c)183 QPixmap NucleotideColorsRenderer::cellImage(char c) {
184 c = (!nucleotideColorScheme.contains(c)) ? 'N' : c;
185 return images.value(c, unknownChar);
186 }
187
cellImage(const U2AssemblyRead &,char c)188 QPixmap NucleotideColorsRenderer::cellImage(const U2AssemblyRead &, char c) {
189 return cellImage(c);
190 }
191
cellImage(const U2AssemblyRead &,char c,char)192 QPixmap NucleotideColorsRenderer::cellImage(const U2AssemblyRead &, char c, char) {
193 return cellImage(c);
194 }
195
196 class ComplementColorsRenderer : public AssemblyCellRenderer {
197 public:
ComplementColorsRenderer()198 ComplementColorsRenderer()
199 : AssemblyCellRenderer(),
200 directImages(), complementImages(), unknownChar(),
201 size(), devicePixelRatio(0), text(false), font() {
202 }
203
~ComplementColorsRenderer()204 virtual ~ComplementColorsRenderer() {
205 }
206
207 virtual void render(const QSize &size, int devicePixelRatio, bool text, const QFont &font);
208
209 virtual QPixmap cellImage(char c);
210 virtual QPixmap cellImage(const U2AssemblyRead &read, char c);
211 virtual QPixmap cellImage(const U2AssemblyRead &read, char c, char ref);
212
213 private:
214 void update();
215
216 private:
217 // images cache
218 QHash<char, QPixmap> directImages, complementImages;
219 QPixmap unknownChar;
220
221 // cached cells parameters
222 QSize size;
223 int devicePixelRatio;
224 bool text;
225 QFont font;
226
227 private:
228 static const QColor directColor, complementColor;
229 };
230
231 const QColor ComplementColorsRenderer::directColor("#4EADE1");
232 const QColor ComplementColorsRenderer::complementColor("#70F970");
233
render(const QSize & _size,int _devicePixelRatio,bool _text,const QFont & _font)234 void ComplementColorsRenderer::render(const QSize &_size, int _devicePixelRatio, bool _text, const QFont &_font) {
235 GTIMER(c1, t1, "ComplementColorsRenderer::render");
236
237 if (_size != size || _devicePixelRatio != devicePixelRatio || _text != text || (text && _font != font)) {
238 // update cache
239 size = _size, devicePixelRatio = _devicePixelRatio, text = _text, font = _font;
240 update();
241 }
242 }
243
update()244 void ComplementColorsRenderer::update() {
245 directImages.clear();
246 complementImages.clear();
247
248 foreach (char c, assemblyAlphabet) {
249 QPixmap dimg(size * devicePixelRatio), cimg(size * devicePixelRatio);
250 dimg.setDevicePixelRatio(devicePixelRatio);
251 cimg.setDevicePixelRatio(devicePixelRatio);
252 QColor dcolor = directColor, ccolor = complementColor, textColor = Qt::black;
253
254 if (isGap(c)) {
255 dcolor = ccolor = QColor("#FBFBFB");
256 textColor = Qt::red;
257 }
258
259 drawCell(dimg, size, dcolor, text, c, font, textColor);
260 drawCell(cimg, size, ccolor, text, c, font, textColor);
261
262 directImages.insert(c, dimg);
263 complementImages.insert(c, cimg);
264 }
265
266 unknownChar = QPixmap(size * devicePixelRatio);
267 unknownChar.setDevicePixelRatio(devicePixelRatio);
268 drawCell(unknownChar, size, QColor("#FBFBFB"), text, '?', font, Qt::red);
269 }
270
cellImage(char c)271 QPixmap ComplementColorsRenderer::cellImage(char c) {
272 c = (!nucleotideColorScheme.contains(c)) ? 'N' : c;
273 return directImages.value(c, unknownChar);
274 }
275
cellImage(const U2AssemblyRead & read,char c)276 QPixmap ComplementColorsRenderer::cellImage(const U2AssemblyRead &read, char c) {
277 c = (!nucleotideColorScheme.contains(c)) ? 'N' : c;
278
279 if (ReadFlagsUtils::isComplementaryRead(read->flags)) {
280 return complementImages.value(c, unknownChar);
281 } else {
282 return directImages.value(c, unknownChar);
283 }
284 }
285
cellImage(const U2AssemblyRead & read,char c,char)286 QPixmap ComplementColorsRenderer::cellImage(const U2AssemblyRead &read, char c, char) {
287 return cellImage(read, c);
288 }
289
290 class DiffNucleotideColorsRenderer : public AssemblyCellRenderer {
291 public:
292 DiffNucleotideColorsRenderer();
~DiffNucleotideColorsRenderer()293 virtual ~DiffNucleotideColorsRenderer() {
294 }
295
296 virtual void render(const QSize &size, int devicePixelRatio, bool text, const QFont &font);
297
298 virtual QPixmap cellImage(char c);
299 virtual QPixmap cellImage(const U2AssemblyRead &read, char c);
300 virtual QPixmap cellImage(const U2AssemblyRead &read, char c, char ref);
301
302 private:
303 void update();
304
305 private:
306 QMap<char, QColor> colorScheme;
307
308 // images cache
309 QHash<char, QPixmap> highlightedImages;
310 QHash<char, QPixmap> normalImages;
311 QPixmap unknownChar;
312
313 // cached cells parameters
314 QSize size;
315 int devicePixelRatio;
316 bool text;
317 QFont font;
318 };
319
320 class PairedColorsRenderer : public AssemblyCellRenderer {
321 public:
PairedColorsRenderer()322 PairedColorsRenderer()
323 : AssemblyCellRenderer(),
324 pairedImages(), unpairedImages(), unknownChar(),
325 size(), devicePixelRatio(0), text(false), font() {
326 }
327
~PairedColorsRenderer()328 virtual ~PairedColorsRenderer() {
329 }
330
331 virtual void render(const QSize &size, int devicePixelRatio, bool text, const QFont &font);
332
333 virtual QPixmap cellImage(char c);
334 virtual QPixmap cellImage(const U2AssemblyRead &read, char c);
335 virtual QPixmap cellImage(const U2AssemblyRead &read, char c, char ref);
336
337 private:
338 void update();
339
340 private:
341 // images cache
342 QHash<char, QPixmap> pairedImages, unpairedImages;
343 QPixmap unknownChar;
344
345 // cached cells parameters
346 QSize size;
347 int devicePixelRatio;
348 bool text;
349 QFont font;
350
351 private:
352 static const QColor pairedColor, unpairedColor;
353 };
354
355 const QColor PairedColorsRenderer::pairedColor("#4EE1AD");
356 const QColor PairedColorsRenderer::unpairedColor("#BBBBBB");
357
render(const QSize & _size,int _devicePixelRatio,bool _text,const QFont & _font)358 void PairedColorsRenderer::render(const QSize &_size, int _devicePixelRatio, bool _text, const QFont &_font) {
359 GTIMER(c1, t1, "PairedReadsColorsRenderer::render");
360
361 if (_size != size || _devicePixelRatio != devicePixelRatio || _text != text || (text && _font != font)) {
362 // update cache
363 size = _size, devicePixelRatio = _devicePixelRatio, text = _text, font = _font;
364 update();
365 }
366 }
367
update()368 void PairedColorsRenderer::update() {
369 pairedImages.clear();
370 unpairedImages.clear();
371
372 foreach (char c, assemblyAlphabet) {
373 QPixmap pimg(size * devicePixelRatio), npimg(size * devicePixelRatio);
374 pimg.setDevicePixelRatio(devicePixelRatio);
375 npimg.setDevicePixelRatio(devicePixelRatio);
376 QColor pcolor = pairedColor, ucolor = unpairedColor, textColor = Qt::black;
377
378 if (isGap(c)) {
379 pcolor = ucolor = QColor("#FBFBFB");
380 textColor = Qt::red;
381 }
382
383 drawCell(pimg, size, pcolor, text, c, font, textColor);
384 drawCell(npimg, size, ucolor, text, c, font, textColor);
385
386 pairedImages.insert(c, pimg);
387 unpairedImages.insert(c, npimg);
388 }
389
390 unknownChar = QPixmap(size * devicePixelRatio);
391 unknownChar.setDevicePixelRatio(devicePixelRatio);
392 drawCell(unknownChar, size, QColor("#FBFBFB"), text, '?', font, Qt::red);
393 }
394
cellImage(char c)395 QPixmap PairedColorsRenderer::cellImage(char c) {
396 c = (!nucleotideColorScheme.contains(c)) ? 'N' : c;
397 return unpairedImages.value(c, unknownChar);
398 }
399
cellImage(const U2AssemblyRead & read,char c)400 QPixmap PairedColorsRenderer::cellImage(const U2AssemblyRead &read, char c) {
401 c = (!nucleotideColorScheme.contains(c)) ? 'N' : c;
402
403 if (ReadFlagsUtils::isPairedRead(read->flags)) {
404 return pairedImages.value(c, unknownChar);
405 } else {
406 return unpairedImages.value(c, unknownChar);
407 }
408 }
409
cellImage(const U2AssemblyRead & read,char c,char)410 QPixmap PairedColorsRenderer::cellImage(const U2AssemblyRead &read, char c, char) {
411 return cellImage(read, c);
412 }
413
DiffNucleotideColorsRenderer()414 DiffNucleotideColorsRenderer::DiffNucleotideColorsRenderer()
415 : AssemblyCellRenderer(), colorScheme(nucleotideColorScheme),
416 highlightedImages(), normalImages(), unknownChar(), size(), devicePixelRatio(0), text(false), font() {
417 }
418
render(const QSize & _size,int _devicePixelRatio,bool _text,const QFont & _font)419 void DiffNucleotideColorsRenderer::render(const QSize &_size, int _devicePixelRatio, bool _text, const QFont &_font) {
420 GTIMER(c1, t1, "DiffNucleotideColorsRenderer::render");
421
422 if (_size != size || _devicePixelRatio != devicePixelRatio || _text != text || (text && _font != font)) {
423 // update cache
424 size = _size, devicePixelRatio = _devicePixelRatio, text = _text, font = _font;
425 update();
426 }
427 }
428
update()429 void DiffNucleotideColorsRenderer::update() {
430 highlightedImages.clear();
431 normalImages.clear();
432
433 QColor normalColor("#BBBBBB");
434
435 foreach (char c, colorScheme.keys()) {
436 QPixmap himg(size * devicePixelRatio), nimg(size * devicePixelRatio);
437 himg.setDevicePixelRatio(devicePixelRatio);
438 nimg.setDevicePixelRatio(devicePixelRatio);
439 // make gaps more noticeable
440 QColor textColor = isGap(c) ? Qt::white : Qt::black;
441 QColor highlightColor = isGap(c) ? QColor("#CC4E4E") : colorScheme.value(c);
442
443 if (extendedPairs.contains(c)) {
444 // char from extended alphabet, draw gradient
445 TwoChars pair = extendedPairs.value(c);
446 drawCell(himg, size, colorScheme.value(pair.first), colorScheme.value(pair.second), text, c, font, textColor);
447 } else {
448 // normal char
449 drawCell(himg, size, highlightColor, text, c, font, textColor);
450 }
451 drawCell(nimg, size, normalColor, text, c, font, textColor);
452
453 highlightedImages.insert(c, himg);
454 normalImages.insert(c, nimg);
455 }
456
457 unknownChar = QPixmap(size * devicePixelRatio);
458 unknownChar.setDevicePixelRatio(devicePixelRatio);
459 drawCell(unknownChar, size, QColor("#FBFBFB"), text, '?', font, Qt::red);
460 }
461
cellImage(char c)462 QPixmap DiffNucleotideColorsRenderer::cellImage(char c) {
463 c = (!nucleotideColorScheme.contains(c)) ? 'N' : c;
464 return highlightedImages.value(c, unknownChar);
465 }
466
cellImage(const U2AssemblyRead &,char c)467 QPixmap DiffNucleotideColorsRenderer::cellImage(const U2AssemblyRead &, char c) {
468 return cellImage(c);
469 }
470
cellImage(const U2AssemblyRead &,char c,char ref)471 QPixmap DiffNucleotideColorsRenderer::cellImage(const U2AssemblyRead &, char c, char ref) {
472 c = (!nucleotideColorScheme.contains(c)) ? 'N' : c;
473
474 if (c == ref) {
475 return normalImages.value(c, unknownChar);
476 } else {
477 return highlightedImages.value(c, unknownChar);
478 }
479 return cellImage(c);
480 }
481
482 //////////////////////////////////////////////////////////////////////////
483 // factories
484
AssemblyCellRendererFactory(const QString & _id,const QString & _name)485 AssemblyCellRendererFactory::AssemblyCellRendererFactory(const QString &_id, const QString &_name)
486 : id(_id), name(_name) {
487 }
488
489 QString AssemblyCellRendererFactory::ALL_NUCLEOTIDES = "ASSEMBLY_RENDERER_ALL_NUCLEOTIDES";
490 QString AssemblyCellRendererFactory::DIFF_NUCLEOTIDES = "ASSEMBLY_RENDERER_DIFF_NUCLEOTIDES";
491 QString AssemblyCellRendererFactory::STRAND_DIRECTION = "ASSEMBLY_RENDERER_STRAND_DIRECTION";
492 QString AssemblyCellRendererFactory::PAIRED = "ASSEMBLY_RENDERER_PAIRED";
493
494 class NucleotideColorsRendererFactory : public AssemblyCellRendererFactory {
495 public:
NucleotideColorsRendererFactory(const QString & _id,const QString & _name)496 NucleotideColorsRendererFactory(const QString &_id, const QString &_name)
497 : AssemblyCellRendererFactory(_id, _name) {
498 }
499
create()500 virtual AssemblyCellRenderer *create() {
501 return new NucleotideColorsRenderer();
502 }
503 };
504
505 class DiffNucleotideColorsRendererFactory : public AssemblyCellRendererFactory {
506 public:
DiffNucleotideColorsRendererFactory(const QString & _id,const QString & _name)507 DiffNucleotideColorsRendererFactory(const QString &_id, const QString &_name)
508 : AssemblyCellRendererFactory(_id, _name) {
509 }
510
create()511 virtual AssemblyCellRenderer *create() {
512 return new DiffNucleotideColorsRenderer();
513 }
514 };
515
516 class ComplementColorsRendererFactory : public AssemblyCellRendererFactory {
517 public:
ComplementColorsRendererFactory(const QString & _id,const QString & _name)518 ComplementColorsRendererFactory(const QString &_id, const QString &_name)
519 : AssemblyCellRendererFactory(_id, _name) {
520 }
521
create()522 virtual AssemblyCellRenderer *create() {
523 return new ComplementColorsRenderer();
524 }
525 };
526
527 class PairedColorsRendererFactory : public AssemblyCellRendererFactory {
528 public:
PairedColorsRendererFactory(const QString & _id,const QString & _name)529 PairedColorsRendererFactory(const QString &_id, const QString &_name)
530 : AssemblyCellRendererFactory(_id, _name) {
531 }
532
create()533 virtual AssemblyCellRenderer *create() {
534 return new PairedColorsRenderer();
535 }
536 };
537
538 //////////////////////////////////////////////////////////////////////////
539 // registry
540
AssemblyCellRendererFactoryRegistry(QObject * parent)541 AssemblyCellRendererFactoryRegistry::AssemblyCellRendererFactoryRegistry(QObject *parent)
542 : QObject(parent) {
543 initBuiltInRenderers();
544 }
545
getFactoryById(const QString & id) const546 AssemblyCellRendererFactory *AssemblyCellRendererFactoryRegistry::getFactoryById(const QString &id) const {
547 foreach (AssemblyCellRendererFactory *f, factories) {
548 if (f->getId() == id) {
549 return f;
550 }
551 }
552 return nullptr;
553 }
554
addFactory(AssemblyCellRendererFactory * f)555 void AssemblyCellRendererFactoryRegistry::addFactory(AssemblyCellRendererFactory *f) {
556 assert(getFactoryById(f->getId()) == nullptr);
557 factories << f;
558 }
559
initBuiltInRenderers()560 void AssemblyCellRendererFactoryRegistry::initBuiltInRenderers() {
561 addFactory(new NucleotideColorsRendererFactory(AssemblyCellRendererFactory::ALL_NUCLEOTIDES,
562 tr("Nucleotide")));
563 addFactory(new DiffNucleotideColorsRendererFactory(AssemblyCellRendererFactory::DIFF_NUCLEOTIDES,
564 tr("Difference")));
565 addFactory(new ComplementColorsRendererFactory(AssemblyCellRendererFactory::STRAND_DIRECTION,
566 tr("Strand direction")));
567 addFactory(new PairedColorsRendererFactory(AssemblyCellRendererFactory::PAIRED,
568 tr("Paired reads")));
569 }
570
~AssemblyCellRendererFactoryRegistry()571 AssemblyCellRendererFactoryRegistry::~AssemblyCellRendererFactoryRegistry() {
572 foreach (AssemblyCellRendererFactory *f, factories) {
573 delete f;
574 }
575 }
576
577 } // namespace U2
578