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