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