Merge branch 'develop' into features/JAL-2094_colourInterface
[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.api.ColorI;
25 import jalview.datamodel.SequenceFeature;
26 import jalview.datamodel.SequenceI;
27 import jalview.schemes.Colour;
28 import jalview.viewmodel.seqfeatures.FeatureRendererModel;
29
30 import java.awt.AlphaComposite;
31 import java.awt.Color;
32 import java.awt.FontMetrics;
33 import java.awt.Graphics;
34 import java.awt.Graphics2D;
35 import java.awt.image.BufferedImage;
36
37 public class FeatureRenderer extends FeatureRendererModel
38 {
39
40   FontMetrics fm;
41
42   int charOffset;
43
44   boolean offscreenRender = false;
45
46   protected SequenceI lastSeq;
47
48   char s;
49
50   int i;
51
52   int av_charHeight, av_charWidth;
53
54   boolean av_validCharWidth, av_isShowSeqFeatureHeight;
55
56   private Integer currentColour;
57
58   /**
59    * Constructor given a viewport
60    * 
61    * @param viewport
62    */
63   public FeatureRenderer(AlignViewportI viewport)
64   {
65     this.av = viewport;
66   }
67
68   protected void updateAvConfig()
69   {
70     av_charHeight = av.getCharHeight();
71     av_charWidth = av.getCharWidth();
72     av_validCharWidth = av.isValidCharWidth();
73     av_isShowSeqFeatureHeight = av.isShowSequenceFeaturesHeight();
74   }
75
76   void renderFeature(Graphics g, SequenceI seq, int fstart, int fend,
77           Color featureColour, int start, int end, int y1)
78   {
79     updateAvConfig();
80     if (((fstart <= end) && (fend >= start)))
81     {
82       if (fstart < start)
83       { // fix for if the feature we have starts before the sequence start,
84         fstart = start; // but the feature end is still valid!!
85       }
86
87       if (fend >= end)
88       {
89         fend = end;
90       }
91       int pady = (y1 + av_charHeight) - av_charHeight / 5;
92       for (i = fstart; i <= fend; i++)
93       {
94         s = seq.getCharAt(i);
95
96         if (jalview.util.Comparison.isGap(s))
97         {
98           continue;
99         }
100
101         g.setColor(featureColour);
102
103         g.fillRect((i - start) * av_charWidth, y1, av_charWidth,
104                 av_charHeight);
105
106         if (offscreenRender || !av_validCharWidth)
107         {
108           continue;
109         }
110
111         g.setColor(Color.white);
112         charOffset = (av_charWidth - fm.charWidth(s)) / 2;
113         g.drawString(String.valueOf(s), charOffset
114                 + (av_charWidth * (i - start)), pady);
115
116       }
117     }
118   }
119
120   void renderScoreFeature(Graphics g, SequenceI seq, int fstart, int fend,
121           Color featureColour, int start, int end, int y1, byte[] bs)
122   {
123     updateAvConfig();
124     if (((fstart <= end) && (fend >= start)))
125     {
126       if (fstart < start)
127       { // fix for if the feature we have starts before the sequence start,
128         fstart = start; // but the feature end is still valid!!
129       }
130
131       if (fend >= end)
132       {
133         fend = end;
134       }
135       int pady = (y1 + av_charHeight) - av_charHeight / 5;
136       int ystrt = 0, yend = av_charHeight;
137       if (bs[0] != 0)
138       {
139         // signed - zero is always middle of residue line.
140         if (bs[1] < 128)
141         {
142           yend = av_charHeight * (128 - bs[1]) / 512;
143           ystrt = av_charHeight - yend / 2;
144         }
145         else
146         {
147           ystrt = av_charHeight / 2;
148           yend = av_charHeight * (bs[1] - 128) / 512;
149         }
150       }
151       else
152       {
153         yend = av_charHeight * bs[1] / 255;
154         ystrt = av_charHeight - yend;
155
156       }
157       for (i = fstart; i <= fend; i++)
158       {
159         s = seq.getCharAt(i);
160
161         if (jalview.util.Comparison.isGap(s))
162         {
163           continue;
164         }
165
166         g.setColor(featureColour);
167         int x = (i - start) * av_charWidth;
168         g.drawRect(x, y1, av_charWidth, av_charHeight);
169         g.fillRect(x, y1 + ystrt, av_charWidth, yend);
170
171         if (offscreenRender || !av_validCharWidth)
172         {
173           continue;
174         }
175
176         g.setColor(Color.black);
177         charOffset = (av_charWidth - fm.charWidth(s)) / 2;
178         g.drawString(String.valueOf(s), charOffset
179                 + (av_charWidth * (i - start)), pady);
180       }
181     }
182   }
183
184   BufferedImage offscreenImage;
185
186   @Override
187   public ColorI findFeatureColour(ColorI initialCol, SequenceI seq, int res)
188   {
189     return new Colour(findFeatureColour(initialCol.getRGB(), seq, res));
190   }
191
192   public ColorI findFeatureColour(Color initialCol, SequenceI seq, int res)
193   {
194     return findFeatureColour(new Colour(initialCol), seq, res);
195   }
196
197   /**
198    * This is used by the Molecule Viewer and Overview to get the accurate colour
199    * of the rendered sequence
200    */
201   public synchronized int findFeatureColour(int initialCol,
202           final SequenceI seq, int column)
203   {
204     if (!av.isShowSequenceFeatures())
205     {
206       return initialCol;
207     }
208
209     SequenceFeature[] sequenceFeatures = seq.getSequenceFeatures();
210     if (seq != lastSeq)
211     {
212       lastSeq = seq;
213       lastSequenceFeatures = sequenceFeatures;
214       if (lastSequenceFeatures != null)
215       {
216         sfSize = lastSequenceFeatures.length;
217       }
218     }
219     else
220     {
221       if (lastSequenceFeatures != sequenceFeatures)
222       {
223         lastSequenceFeatures = sequenceFeatures;
224         if (lastSequenceFeatures != null)
225         {
226           sfSize = lastSequenceFeatures.length;
227         }
228       }
229     }
230
231     if (lastSequenceFeatures == null || sfSize == 0)
232     {
233       return initialCol;
234     }
235
236     if (jalview.util.Comparison.isGap(lastSeq.getCharAt(column)))
237     {
238       return Color.white.getRGB();
239     }
240
241     // Only bother making an offscreen image if transparency is applied
242     if (transparency != 1.0f && offscreenImage == null)
243     {
244       offscreenImage = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB);
245     }
246
247     currentColour = null;
248     // TODO: non-threadsafe - each rendering thread needs its own instance of
249     // the feature renderer - or this should be synchronized.
250     offscreenRender = true;
251
252     if (offscreenImage != null)
253     {
254       offscreenImage.setRGB(0, 0, initialCol);
255       drawSequence(offscreenImage.getGraphics(), lastSeq, column, column, 0);
256
257       return offscreenImage.getRGB(0, 0);
258     }
259     else
260     {
261       drawSequence(null, lastSeq, lastSeq.findPosition(column), -1, -1);
262
263       if (currentColour == null)
264       {
265         return initialCol;
266       }
267       else
268       {
269         return currentColour.intValue();
270       }
271     }
272
273   }
274
275   private volatile SequenceFeature[] lastSequenceFeatures;
276
277   int sfSize;
278
279   int sfindex;
280
281   int spos;
282
283   int epos;
284
285   public synchronized void drawSequence(Graphics g, final SequenceI seq,
286           int start, int end, int y1)
287   {
288     SequenceFeature[] sequenceFeatures = seq.getSequenceFeatures();
289     if (sequenceFeatures == null || sequenceFeatures.length == 0)
290     {
291       return;
292     }
293
294     if (g != null)
295     {
296       fm = g.getFontMetrics();
297     }
298
299     updateFeatures();
300
301     if (lastSeq == null || seq != lastSeq
302             || sequenceFeatures != lastSequenceFeatures)
303     {
304       lastSeq = seq;
305       lastSequenceFeatures = sequenceFeatures;
306     }
307
308     if (transparency != 1 && g != null)
309     {
310       Graphics2D g2 = (Graphics2D) g;
311       g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
312               transparency));
313     }
314
315     if (!offscreenRender)
316     {
317       spos = lastSeq.findPosition(start);
318       epos = lastSeq.findPosition(end);
319     }
320
321     sfSize = lastSequenceFeatures.length;
322     String type;
323     for (int renderIndex = 0; renderIndex < renderOrder.length; renderIndex++)
324     {
325       type = renderOrder[renderIndex];
326
327       if (type == null || !showFeatureOfType(type))
328       {
329         continue;
330       }
331
332       // loop through all features in sequence to find
333       // current feature to render
334       for (sfindex = 0; sfindex < sfSize; sfindex++)
335       {
336         final SequenceFeature sequenceFeature = lastSequenceFeatures[sfindex];
337         if (!sequenceFeature.type.equals(type))
338         {
339           continue;
340         }
341
342         if (featureGroups != null
343                 && sequenceFeature.featureGroup != null
344                 && sequenceFeature.featureGroup.length() != 0
345                 && featureGroups.containsKey(sequenceFeature.featureGroup)
346                 && !featureGroups.get(sequenceFeature.featureGroup)
347                         .booleanValue())
348         {
349           continue;
350         }
351
352         if (!offscreenRender
353                 && (sequenceFeature.getBegin() > epos || sequenceFeature
354                         .getEnd() < spos))
355         {
356           continue;
357         }
358
359         if (offscreenRender && offscreenImage == null)
360         {
361           if (sequenceFeature.begin <= start
362                   && sequenceFeature.end >= start)
363           {
364             // this is passed out to the overview and other sequence renderers
365             // (e.g. molecule viewer) to get displayed colour for rendered
366             // sequence
367             currentColour = new Integer(getColour(sequenceFeature).getRGB());
368             // used to be retreived from av.featuresDisplayed
369             // currentColour = av.featuresDisplayed
370             // .get(sequenceFeatures[sfindex].type);
371
372           }
373         }
374         else if (sequenceFeature.type.equals("disulfide bond"))
375         {
376           renderFeature(g, seq, seq.findIndex(sequenceFeature.begin) - 1,
377                   seq.findIndex(sequenceFeature.begin) - 1,
378                   getColour(sequenceFeature)
379                   // new Color(((Integer) av.featuresDisplayed
380                   // .get(sequenceFeatures[sfindex].type)).intValue())
381                   , start, end, y1);
382           renderFeature(g, seq, seq.findIndex(sequenceFeature.end) - 1,
383                   seq.findIndex(sequenceFeature.end) - 1,
384                   getColour(sequenceFeature)
385                   // new Color(((Integer) av.featuresDisplayed
386                   // .get(sequenceFeatures[sfindex].type)).intValue())
387                   , start, end, y1);
388
389         }
390         else if (showFeature(sequenceFeature))
391         {
392           if (av_isShowSeqFeatureHeight
393                   && !Float.isNaN(sequenceFeature.score))
394           {
395             renderScoreFeature(g, seq,
396                     seq.findIndex(sequenceFeature.begin) - 1,
397                     seq.findIndex(sequenceFeature.end) - 1,
398                     getColour(sequenceFeature), start, end, y1,
399                     normaliseScore(sequenceFeature));
400           }
401           else
402           {
403             renderFeature(g, seq, seq.findIndex(sequenceFeature.begin) - 1,
404                     seq.findIndex(sequenceFeature.end) - 1,
405                     getColour(sequenceFeature), start, end, y1);
406           }
407         }
408
409       }
410
411     }
412
413     if (transparency != 1.0f && g != null)
414     {
415       Graphics2D g2 = (Graphics2D) g;
416       g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
417               1.0f));
418     }
419   }
420
421   /**
422    * Called when alignment in associated view has new/modified features to
423    * discover and display.
424    * 
425    */
426   @Override
427   public void featuresAdded()
428   {
429     lastSeq = null;
430     findAllFeatures();
431   }
432 }