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