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