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