JAL-2094 new classes ColorI, Colour added
[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.ColorI;
24 import jalview.datamodel.SequenceFeature;
25 import jalview.datamodel.SequenceI;
26 import jalview.schemes.Colour;
27 import jalview.viewmodel.seqfeatures.FeatureRendererModel;
28
29 import java.awt.AlphaComposite;
30 import java.awt.Color;
31 import java.awt.FontMetrics;
32 import java.awt.Graphics;
33 import java.awt.Graphics2D;
34 import java.awt.image.BufferedImage;
35
36 public class FeatureRenderer extends FeatureRendererModel
37 {
38
39   FontMetrics fm;
40
41   int charOffset;
42
43   boolean offscreenRender = false;
44
45   protected SequenceI lastSeq;
46
47   char s;
48
49   int i;
50
51   int av_charHeight, av_charWidth;
52
53   boolean av_validCharWidth, av_isShowSeqFeatureHeight;
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 ColorI findFeatureColour(ColorI initialCol, SequenceI seq, int res)
175   {
176     return new Colour(findFeatureColour(initialCol.getRGB(), seq, res));
177   }
178
179   public ColorI findFeatureColour(Color initialCol, SequenceI seq, int res)
180   {
181     return findFeatureColour(new Colour(initialCol), seq, res);
182   }
183
184   /**
185    * This is used by the Molecule Viewer and Overview to get the accurate colour
186    * of the rendered sequence
187    */
188   public synchronized int findFeatureColour(int initialCol,
189           final SequenceI seq, int column)
190   {
191     if (!av.isShowSequenceFeatures())
192     {
193       return initialCol;
194     }
195
196     SequenceFeature[] sequenceFeatures = seq.getSequenceFeatures();
197     if (seq != lastSeq)
198     {
199       lastSeq = seq;
200       lastSequenceFeatures = sequenceFeatures;
201       if (lastSequenceFeatures != null)
202       {
203         sfSize = lastSequenceFeatures.length;
204       }
205     }
206     else
207     {
208       if (lastSequenceFeatures != sequenceFeatures)
209       {
210         lastSequenceFeatures = sequenceFeatures;
211         if (lastSequenceFeatures != null)
212         {
213           sfSize = lastSequenceFeatures.length;
214         }
215       }
216     }
217
218     if (lastSequenceFeatures == null || sfSize == 0)
219     {
220       return initialCol;
221     }
222
223     if (jalview.util.Comparison.isGap(lastSeq.getCharAt(column)))
224     {
225       return Color.white.getRGB();
226     }
227
228     // Only bother making an offscreen image if transparency is applied
229     if (transparency != 1.0f && offscreenImage == null)
230     {
231       offscreenImage = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB);
232     }
233
234     currentColour = null;
235     // TODO: non-threadsafe - each rendering thread needs its own instance of
236     // the feature renderer - or this should be synchronized.
237     offscreenRender = true;
238
239     if (offscreenImage != null)
240     {
241       offscreenImage.setRGB(0, 0, initialCol);
242       drawSequence(offscreenImage.getGraphics(), lastSeq, column, column, 0);
243
244       return offscreenImage.getRGB(0, 0);
245     }
246     else
247     {
248       drawSequence(null, lastSeq, lastSeq.findPosition(column), -1, -1);
249
250       if (currentColour == null)
251       {
252         return initialCol;
253       }
254       else
255       {
256         return currentColour.intValue();
257       }
258     }
259
260   }
261
262   private volatile SequenceFeature[] lastSequenceFeatures;
263
264   int sfSize;
265
266   int sfindex;
267
268   int spos;
269
270   int epos;
271
272   private Integer currentColour;
273
274   public synchronized void drawSequence(Graphics g, final SequenceI seq,
275           int start, int end, int y1)
276   {
277     SequenceFeature[] sequenceFeatures = seq.getSequenceFeatures();
278     if (sequenceFeatures == null || sequenceFeatures.length == 0)
279     {
280       return;
281     }
282
283     if (g != null)
284     {
285       fm = g.getFontMetrics();
286     }
287
288     updateFeatures();
289
290     if (lastSeq == null || seq != lastSeq
291             || sequenceFeatures != lastSequenceFeatures)
292     {
293       lastSeq = seq;
294       lastSequenceFeatures = sequenceFeatures;
295     }
296
297     if (transparency != 1 && g != null)
298     {
299       Graphics2D g2 = (Graphics2D) g;
300       g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
301               transparency));
302     }
303
304     if (!offscreenRender)
305     {
306       spos = lastSeq.findPosition(start);
307       epos = lastSeq.findPosition(end);
308     }
309
310     sfSize = lastSequenceFeatures.length;
311     String type;
312     for (int renderIndex = 0; renderIndex < renderOrder.length; renderIndex++)
313     {
314       type = renderOrder[renderIndex];
315
316       if (type == null || !showFeatureOfType(type))
317       {
318         continue;
319       }
320
321       // loop through all features in sequence to find
322       // current feature to render
323       for (sfindex = 0; sfindex < sfSize; sfindex++)
324       {
325         final SequenceFeature sequenceFeature = lastSequenceFeatures[sfindex];
326         if (!sequenceFeature.type.equals(type))
327         {
328           continue;
329         }
330
331         if (featureGroups != null
332                 && sequenceFeature.featureGroup != null
333                 && sequenceFeature.featureGroup.length() != 0
334                 && featureGroups.containsKey(sequenceFeature.featureGroup)
335                 && !featureGroups.get(sequenceFeature.featureGroup)
336                         .booleanValue())
337         {
338           continue;
339         }
340
341         if (!offscreenRender
342                 && (sequenceFeature.getBegin() > epos || sequenceFeature
343                         .getEnd() < spos))
344         {
345           continue;
346         }
347
348         if (offscreenRender && offscreenImage == null)
349         {
350           if (sequenceFeature.begin <= start
351                   && sequenceFeature.end >= start)
352           {
353             // this is passed out to the overview and other sequence renderers
354             // (e.g. molecule viewer) to get displayed colour for rendered
355             // sequence
356             currentColour = new Integer(getColour(sequenceFeature).getRGB());
357             // used to be retreived from av.featuresDisplayed
358             // currentColour = av.featuresDisplayed
359             // .get(sequenceFeatures[sfindex].type);
360
361           }
362         }
363         else if (sequenceFeature.type.equals("disulfide bond"))
364         {
365           renderFeature(g, seq, seq.findIndex(sequenceFeature.begin) - 1,
366                   seq.findIndex(sequenceFeature.begin) - 1,
367                   getColour(sequenceFeature)
368                   // new Color(((Integer) av.featuresDisplayed
369                   // .get(sequenceFeatures[sfindex].type)).intValue())
370                   , start, end, y1);
371           renderFeature(g, seq, seq.findIndex(sequenceFeature.end) - 1,
372                   seq.findIndex(sequenceFeature.end) - 1,
373                   getColour(sequenceFeature)
374                   // new Color(((Integer) av.featuresDisplayed
375                   // .get(sequenceFeatures[sfindex].type)).intValue())
376                   , start, end, y1);
377
378         }
379         else if (showFeature(sequenceFeature))
380         {
381           if (av_isShowSeqFeatureHeight
382                   && !Float.isNaN(sequenceFeature.score))
383           {
384             renderScoreFeature(g, seq,
385                     seq.findIndex(sequenceFeature.begin) - 1,
386                     seq.findIndex(sequenceFeature.end) - 1,
387                     getColour(sequenceFeature), start, end, y1,
388                     normaliseScore(sequenceFeature));
389           }
390           else
391           {
392             renderFeature(g, seq, seq.findIndex(sequenceFeature.begin) - 1,
393                     seq.findIndex(sequenceFeature.end) - 1,
394                     getColour(sequenceFeature), start, end, y1);
395           }
396         }
397
398       }
399
400     }
401
402     if (transparency != 1.0f && g != null)
403     {
404       Graphics2D g2 = (Graphics2D) g;
405       g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
406               1.0f));
407     }
408   }
409
410   /**
411    * Called when alignment in associated view has new/modified features to
412    * discover and display.
413    * 
414    */
415   @Override
416   public void featuresAdded()
417   {
418     lastSeq = null;
419     findAllFeatures();
420   }
421 }