07d97d01e5105aa70528c1cdf5c7867cb3c26f4e
[jalview.git] / src / jalview / renderer / seqfeatures / FeatureRenderer.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3  * Copyright (C) $$Year-Rel$$ The Jalview Authors
4  * 
5  * This file is part of Jalview.
6  * 
7  * Jalview is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License 
9  * as published by the Free Software Foundation, either version 3
10  * of the License, or (at your option) any later version.
11  *  
12  * Jalview is distributed in the hope that it will be useful, but 
13  * WITHOUT ANY WARRANTY; without even the implied warranty 
14  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
15  * PURPOSE.  See the GNU General Public License for more details.
16  * 
17  * You should have received a copy of the GNU General Public License
18  * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
19  * The Jalview Authors are detailed in the 'AUTHORS' file.
20  */
21 package jalview.renderer.seqfeatures;
22
23 import jalview.api.AlignViewportI;
24 import jalview.datamodel.SequenceFeature;
25 import jalview.datamodel.SequenceI;
26 import jalview.viewmodel.seqfeatures.FeatureRendererModel;
27
28 import java.awt.AlphaComposite;
29 import java.awt.Color;
30 import java.awt.FontMetrics;
31 import java.awt.Graphics;
32 import java.awt.Graphics2D;
33 import java.awt.image.BufferedImage;
34
35 public class FeatureRenderer extends FeatureRendererModel
36 {
37
38   FontMetrics fm;
39
40   int charOffset;
41
42   boolean offscreenRender = false;
43
44   protected SequenceI lastSeq;
45
46   char s;
47
48   int i;
49
50   int av_charHeight, av_charWidth;
51
52   boolean av_validCharWidth, av_isShowSeqFeatureHeight;
53
54   private Integer currentColour;
55
56   /**
57    * Constructor given a viewport
58    * 
59    * @param viewport
60    */
61   public FeatureRenderer(AlignViewportI viewport)
62   {
63     this.av = viewport;
64   }
65
66   protected void updateAvConfig()
67   {
68     av_charHeight = av.getCharHeight();
69     av_charWidth = av.getCharWidth();
70     av_validCharWidth = av.isValidCharWidth();
71     av_isShowSeqFeatureHeight = av.isShowSequenceFeaturesHeight();
72   }
73
74   void renderFeature(Graphics g, SequenceI seq, int fstart, int fend,
75           Color featureColour, int start, int end, int y1)
76   {
77     updateAvConfig();
78     if (((fstart <= end) && (fend >= start)))
79     {
80       if (fstart < start)
81       { // fix for if the feature we have starts before the sequence start,
82         fstart = start; // but the feature end is still valid!!
83       }
84
85       if (fend >= end)
86       {
87         fend = end;
88       }
89       int pady = (y1 + av_charHeight) - av_charHeight / 5;
90       for (i = fstart; i <= fend; i++)
91       {
92         s = seq.getCharAt(i);
93
94         if (jalview.util.Comparison.isGap(s))
95         {
96           continue;
97         }
98
99         g.setColor(featureColour);
100
101         g.fillRect((i - start) * av_charWidth, y1, av_charWidth,
102                 av_charHeight);
103
104         if (offscreenRender || !av_validCharWidth)
105         {
106           continue;
107         }
108
109         g.setColor(Color.white);
110         charOffset = (av_charWidth - fm.charWidth(s)) / 2;
111         g.drawString(String.valueOf(s), charOffset
112                 + (av_charWidth * (i - start)), pady);
113
114       }
115     }
116   }
117
118   void renderScoreFeature(Graphics g, SequenceI seq, int fstart, int fend,
119           Color featureColour, int start, int end, int y1, byte[] bs)
120   {
121     updateAvConfig();
122     if (((fstart <= end) && (fend >= start)))
123     {
124       if (fstart < start)
125       { // fix for if the feature we have starts before the sequence start,
126         fstart = start; // but the feature end is still valid!!
127       }
128
129       if (fend >= end)
130       {
131         fend = end;
132       }
133       int pady = (y1 + av_charHeight) - av_charHeight / 5;
134       int ystrt = 0, yend = av_charHeight;
135       if (bs[0] != 0)
136       {
137         // signed - zero is always middle of residue line.
138         if (bs[1] < 128)
139         {
140           yend = av_charHeight * (128 - bs[1]) / 512;
141           ystrt = av_charHeight - yend / 2;
142         }
143         else
144         {
145           ystrt = av_charHeight / 2;
146           yend = av_charHeight * (bs[1] - 128) / 512;
147         }
148       }
149       else
150       {
151         yend = av_charHeight * bs[1] / 255;
152         ystrt = av_charHeight - yend;
153
154       }
155       for (i = fstart; i <= fend; i++)
156       {
157         s = seq.getCharAt(i);
158
159         if (jalview.util.Comparison.isGap(s))
160         {
161           continue;
162         }
163
164         g.setColor(featureColour);
165         int x = (i - start) * av_charWidth;
166         g.drawRect(x, y1, av_charWidth, av_charHeight);
167         g.fillRect(x, y1 + ystrt, av_charWidth, yend);
168
169         if (offscreenRender || !av_validCharWidth)
170         {
171           continue;
172         }
173
174         g.setColor(Color.black);
175         charOffset = (av_charWidth - fm.charWidth(s)) / 2;
176         g.drawString(String.valueOf(s), charOffset
177                 + (av_charWidth * (i - start)), pady);
178       }
179     }
180   }
181
182   BufferedImage offscreenImage;
183
184   @Override
185   public Color findFeatureColour(Color initialCol, SequenceI seq, int res)
186   {
187     return new Color(findFeatureColour(initialCol.getRGB(), seq, res));
188   }
189
190   /**
191    * This is used by Structure Viewers and the Overview Window to get the
192    * feature colour of the rendered sequence, returned as an RGB value
193    * 
194    * @param defaultColour
195    * @param seq
196    * @param column
197    * @return
198    */
199   public synchronized int findFeatureColour(int defaultColour,
200           final SequenceI seq, int column)
201   {
202     if (!av.isShowSequenceFeatures())
203     {
204       return defaultColour;
205     }
206
207     SequenceFeature[] sequenceFeatures = seq.getSequenceFeatures();
208     if (seq != lastSeq)
209     {
210       lastSeq = seq;
211       lastSequenceFeatures = sequenceFeatures;
212       if (lastSequenceFeatures != null)
213       {
214         sfSize = lastSequenceFeatures.length;
215       }
216     }
217     else
218     {
219       if (lastSequenceFeatures != sequenceFeatures)
220       {
221         lastSequenceFeatures = sequenceFeatures;
222         if (lastSequenceFeatures != null)
223         {
224           sfSize = lastSequenceFeatures.length;
225         }
226       }
227     }
228
229     if (lastSequenceFeatures == null || sfSize == 0)
230     {
231       return defaultColour;
232     }
233
234     if (jalview.util.Comparison.isGap(lastSeq.getCharAt(column)))
235     {
236       return Color.white.getRGB();
237     }
238
239     // Only bother making an offscreen image if transparency is applied
240     if (transparency != 1.0f && offscreenImage == null)
241     {
242       offscreenImage = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB);
243     }
244
245     currentColour = null;
246     // TODO: non-threadsafe - each rendering thread needs its own instance of
247     // the feature renderer - or this should be synchronized.
248     offscreenRender = true;
249
250     if (offscreenImage != null)
251     {
252       offscreenImage.setRGB(0, 0, defaultColour);
253       drawSequence(offscreenImage.getGraphics(), lastSeq, column, column, 0);
254
255       return offscreenImage.getRGB(0, 0);
256     }
257     else
258     {
259       drawSequence(null, lastSeq, lastSeq.findPosition(column), -1, -1);
260
261       if (currentColour == null)
262       {
263         return defaultColour;
264       }
265       else
266       {
267         return currentColour.intValue();
268       }
269     }
270
271   }
272
273   private volatile SequenceFeature[] lastSequenceFeatures;
274
275   int sfSize;
276
277   int sfindex;
278
279   int spos;
280
281   int epos;
282
283   /**
284    * Draws the sequence on the graphics context, or just determines the colour
285    * that would be drawn (if flag offscreenrender is true).
286    * 
287    * @param g
288    * @param seq
289    * @param start
290    *          start column (or sequence position in offscreenrender mode)
291    * @param end
292    *          end column (not used in offscreenrender mode)
293    * @param y1
294    *          vertical offset at which to draw on the graphics
295    */
296   public synchronized void drawSequence(Graphics g, final SequenceI seq,
297           int start, int end, int y1)
298   {
299     SequenceFeature[] sequenceFeatures = seq.getSequenceFeatures();
300     if (sequenceFeatures == null || sequenceFeatures.length == 0)
301     {
302       return;
303     }
304
305     if (g != null)
306     {
307       fm = g.getFontMetrics();
308     }
309
310     updateFeatures();
311
312     if (lastSeq == null || seq != lastSeq
313             || sequenceFeatures != lastSequenceFeatures)
314     {
315       lastSeq = seq;
316       lastSequenceFeatures = sequenceFeatures;
317     }
318
319     if (transparency != 1 && g != null)
320     {
321       Graphics2D g2 = (Graphics2D) g;
322       g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
323               transparency));
324     }
325
326     if (!offscreenRender)
327     {
328       spos = lastSeq.findPosition(start);
329       epos = lastSeq.findPosition(end);
330     }
331
332     sfSize = lastSequenceFeatures.length;
333     for (String type : renderOrder)
334     {
335       if (!showFeatureOfType(type))
336       {
337         continue;
338       }
339
340       // loop through all features in sequence to find
341       // current feature to render
342       for (sfindex = 0; sfindex < sfSize; sfindex++)
343       {
344         final SequenceFeature sequenceFeature = lastSequenceFeatures[sfindex];
345         if (!sequenceFeature.type.equals(type))
346         {
347           continue;
348         }
349
350         if (featureGroupNotShown(sequenceFeature))
351         {
352           continue;
353         }
354
355         /*
356          * check feature overlaps the visible part of the alignment, 
357          * unless doing offscreenRender (to the Overview window or a 
358          * structure viewer) which is not limited 
359          */
360         if (!offscreenRender
361                 && (sequenceFeature.getBegin() > epos || sequenceFeature
362                         .getEnd() < spos))
363         {
364           continue;
365         }
366
367         Color featureColour = getColour(sequenceFeature);
368         boolean isDisulfideBond = "disulfide bond".equals(sequenceFeature.type);
369         if (offscreenRender && offscreenImage == null)
370         {
371           /*
372            * offscreen mode with no image (image is only needed if transparency 
373            * is applied to feature colours) - just check feature is rendered at 
374            * the requested position (start == sequence position in this mode)
375            */
376           boolean featureIsAtPosition = sequenceFeature.begin <= start
377                   && sequenceFeature.end >= start;
378           if (isDisulfideBond)
379           {
380             featureIsAtPosition = sequenceFeature.begin == start
381                     || sequenceFeature.end == start;
382           }
383           if (featureIsAtPosition)
384           {
385             // this is passed out to the overview and other sequence renderers
386             // (e.g. molecule viewer) to get displayed colour for rendered
387             // sequence
388             currentColour = new Integer(featureColour.getRGB());
389             // used to be retreived from av.featuresDisplayed
390             // currentColour = av.featuresDisplayed
391             // .get(sequenceFeatures[sfindex].type);
392
393           }
394         }
395         else if (isDisulfideBond)
396         {
397           renderFeature(g, seq, seq.findIndex(sequenceFeature.begin) - 1,
398                   seq.findIndex(sequenceFeature.begin) - 1, featureColour,
399                   start, end, y1);
400           renderFeature(g, seq, seq.findIndex(sequenceFeature.end) - 1,
401                   seq.findIndex(sequenceFeature.end) - 1, featureColour,
402                   start, end, y1);
403
404         }
405         else if (showFeature(sequenceFeature))
406         {
407           if (av_isShowSeqFeatureHeight
408                   && !Float.isNaN(sequenceFeature.score))
409           {
410             renderScoreFeature(g, seq,
411                     seq.findIndex(sequenceFeature.begin) - 1,
412                     seq.findIndex(sequenceFeature.end) - 1,
413                     featureColour, start, end, y1,
414                     normaliseScore(sequenceFeature));
415           }
416           else
417           {
418             renderFeature(g, seq, seq.findIndex(sequenceFeature.begin) - 1,
419                     seq.findIndex(sequenceFeature.end) - 1,
420                     featureColour, start, end, y1);
421           }
422         }
423       }
424     }
425
426     if (transparency != 1.0f && g != null)
427     {
428       Graphics2D g2 = (Graphics2D) g;
429       g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
430               1.0f));
431     }
432   }
433
434   /**
435    * Answers true if the feature belongs to a feature group which is not
436    * currently displayed, else false
437    * 
438    * @param sequenceFeature
439    * @return
440    */
441   protected boolean featureGroupNotShown(
442           final SequenceFeature sequenceFeature)
443   {
444     return featureGroups != null
445             && sequenceFeature.featureGroup != null
446             && sequenceFeature.featureGroup.length() != 0
447             && featureGroups.containsKey(sequenceFeature.featureGroup)
448             && !featureGroups.get(sequenceFeature.featureGroup)
449                     .booleanValue();
450   }
451
452   /**
453    * Called when alignment in associated view has new/modified features to
454    * discover and display.
455    * 
456    */
457   @Override
458   public void featuresAdded()
459   {
460     lastSeq = null;
461     findAllFeatures();
462   }
463 }