JAL-2588 (and JAL-2610, JAL-2603) mid refactor
[jalview.git] / src / jalview / renderer / OverviewRenderer.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;
22
23 import jalview.api.AlignmentColsCollectionI;
24 import jalview.api.AlignmentRowsCollectionI;
25 import jalview.datamodel.AlignmentAnnotation;
26 import jalview.datamodel.AlignmentI;
27 import jalview.datamodel.Annotation;
28 import jalview.datamodel.SequenceGroup;
29 import jalview.datamodel.SequenceI;
30 import jalview.renderer.seqfeatures.FeatureColourFinder;
31 import jalview.renderer.seqfeatures.FeatureRenderer;
32 import jalview.util.Comparison;
33 import jalview.viewmodel.OverviewDimensions;
34
35 import java.awt.Color;
36 import java.awt.Graphics;
37 import java.awt.image.BufferedImage;
38
39 public class OverviewRenderer
40 {
41   private FeatureColourFinder finder;
42
43   private jalview.api.SequenceRenderer sr;
44
45   // image to render on
46   private BufferedImage miniMe;
47
48   // raw number of pixels to allocate to each column
49   private float pixelsPerCol;
50
51   // raw number of pixels to allocate to each row
52   private float pixelsPerSeq;
53
54   // flag to indicate whether to halt drawing
55   private volatile boolean redraw = false;
56
57   // reference to alignment, needed to get sequence groups
58   private AlignmentI al;
59
60   private ResidueShaderI shader;
61
62   public OverviewRenderer(jalview.api.SequenceRenderer seqRenderer,
63           FeatureRenderer fr, OverviewDimensions od, AlignmentI alignment,
64           ResidueShaderI resshader)
65   {
66     sr = seqRenderer;
67     finder = new FeatureColourFinder(fr);
68
69     al = alignment;
70     shader = resshader;
71
72     pixelsPerCol = od.getPixelsPerCol();
73     pixelsPerSeq = od.getPixelsPerSeq();
74     miniMe = new BufferedImage(od.getWidth(), od.getHeight(),
75             BufferedImage.TYPE_INT_RGB);
76   }
77
78   /**
79    * Draw alignment rows and columns onto an image
80    * 
81    * @param rit
82    *          Iterator over rows to be drawn
83    * @param cit
84    *          Iterator over columns to be drawn
85    * @return image containing the drawing
86    */
87   public BufferedImage draw(AlignmentRowsCollectionI rows,
88           AlignmentColsCollectionI cols)
89   {
90     int rgbcolor = Color.white.getRGB();
91     int seqIndex = 0;
92     int pixelRow = 0;
93
94     for (int alignmentRow : rows)
95     {
96       if (redraw)
97       {
98         break;
99       }
100
101       // get details of this alignment row
102       boolean hidden = rows.isHidden(alignmentRow);
103       SequenceI seq = rows.getSequence(alignmentRow);
104       // rate limiting step when rendering overview for lots of groups
105       SequenceGroup[] allGroups = al.findAllGroups(seq);
106
107       // calculate where this row extends to in pixels
108       int endRow = Math.min(Math.round((seqIndex + 1) * pixelsPerSeq) - 1,
109               miniMe.getHeight() - 1);
110
111       int colIndex = 0;
112       int pixelCol = 0;
113       for (int alignmentCol : cols)
114       {
115         if (redraw)
116         {
117           break;
118         }
119
120         // calculate where this column extends to in pixels
121         int endCol = Math.min(
122                 Math.round((colIndex + 1) * pixelsPerCol) - 1,
123                 miniMe.getWidth() - 1);
124
125         // don't do expensive colour determination if we're not going to use it
126         // NB this is important to avoid performance issues in the overview
127         // panel
128         if (pixelCol <= endCol)
129         {
130           // determine the colour based on the sequence and column position
131           rgbcolor = getColumnColourFromSequence(allGroups, seq,
132                   hidden || cols.isHidden(alignmentCol), alignmentCol,
133                   finder);
134
135           // fill in the appropriate number of pixels
136           for (int row = pixelRow; row <= endRow; ++row)
137           {
138             for (int col = pixelCol; col <= endCol; ++col)
139             {
140               miniMe.setRGB(col, row, rgbcolor);
141             }
142           }
143
144           pixelCol = endCol + 1;
145         }
146         colIndex++;
147       }
148       pixelRow = endRow + 1;
149       seqIndex++;
150     }
151     return miniMe;
152   }
153
154   /*
155    * Find the colour of a sequence at a specified column position
156    */
157   private int getColumnColourFromSequence(SequenceGroup[] allGroups,
158           jalview.datamodel.SequenceI seq,
159           boolean isHidden, int lastcol, FeatureColourFinder fcfinder)
160   {
161     Color color = Color.white;
162
163     if ((seq != null) && (seq.getLength() > lastcol))
164     {
165       color = getResidueColour(allGroups, seq, lastcol, fcfinder);
166     }
167
168     if (isHidden)
169     {
170       color = color.darker().darker();
171     }
172
173     return color.getRGB();
174   }
175
176   private Color getResidueColour(SequenceGroup[] allGroups,
177           final SequenceI seq, int position,
178           FeatureColourFinder finder)
179   {
180     Color col = getResidueBoxColour(allGroups, seq, position);
181
182     if (finder != null)
183     {
184       col = finder.findFeatureColour(col, seq, position);
185     }
186     return col;
187   }
188
189   protected Color getResidueBoxColour(SequenceGroup[] allGroups,
190           SequenceI seq, int i)
191   {
192
193     ResidueShaderI currentShader;
194
195     SequenceGroup currentSequenceGroup = inCurrentSequenceGroup(allGroups,
196             i);
197     if (currentSequenceGroup != null)
198     {
199       currentShader = currentSequenceGroup.getGroupColourScheme();
200     }
201     else
202     {
203       currentShader = shader;
204     }
205
206     return getBoxColour(currentShader, seq, i);
207   }
208
209   SequenceGroup inCurrentSequenceGroup(SequenceGroup[] allGroups, int res)
210   {
211     if (allGroups == null)
212     {
213       return null;
214     }
215
216     for (int i = 0; i < allGroups.length; i++)
217     {
218       if ((allGroups[i].getStartRes() <= res)
219               && (allGroups[i].getEndRes() >= res))
220       {
221         return (allGroups[i]);
222       }
223     }
224
225     return null;
226   }
227
228   Color getBoxColour(ResidueShaderI shader, SequenceI seq, int i)
229   {
230     Color resBoxColour = Color.white;
231     char currentChar = seq.getCharAt(i);
232
233     if (shader.getColourScheme() != null)
234     {
235       if (Comparison.isGap(currentChar)
236               && !shader.getColourScheme().hasGapColour())
237       {
238         resBoxColour = Color.lightGray;
239       }
240       else
241       {
242         resBoxColour = shader.findColour(currentChar, i, seq);
243       }
244     }
245     else if (Comparison.isGap(currentChar))
246     {
247       resBoxColour = Color.lightGray;
248     }
249
250     return resBoxColour;
251   }
252
253   /**
254    * Draw the alignment annotation in the overview panel
255    * 
256    * @param g
257    *          the graphics object to draw on
258    * @param anno
259    *          alignment annotation information
260    * @param charWidth
261    *          alignment character width value
262    * @param y
263    *          y-position for the annotation graph
264    * @param cols
265    *          the collection of columns used in the overview panel
266    */
267   public void drawGraph(Graphics g, AlignmentAnnotation anno, int charWidth,
268           int y, AlignmentColsCollectionI cols)
269   {
270     Annotation[] annotations = anno.annotations;
271     g.setColor(Color.white);
272     g.fillRect(0, 0, miniMe.getWidth(), y);
273
274     int height;
275     int colIndex = 0;
276     int pixelCol = 0;
277     for (int alignmentCol : cols)
278     {
279       if (redraw)
280       {
281         break;
282       }
283       if (alignmentCol >= annotations.length)
284       {
285         break; // no more annotations to draw here
286       }
287       else
288       {
289         int endCol = Math.min(
290                 Math.round((colIndex + 1) * pixelsPerCol) - 1,
291                 miniMe.getWidth() - 1);
292
293         if (annotations[alignmentCol] != null)
294         {
295           if (annotations[alignmentCol].colour == null)
296           {
297             g.setColor(Color.black);
298           }
299           else
300           {
301             g.setColor(annotations[alignmentCol].colour);
302           }
303
304           height = (int) ((annotations[alignmentCol].value / anno.graphMax) * y);
305           if (height > y)
306           {
307             height = y;
308           }
309
310           g.fillRect(pixelCol, y - height, endCol - pixelCol + 1, height);
311         }
312         pixelCol = endCol + 1;
313         colIndex++;
314       }
315     }
316   }
317
318   public void setRedraw(boolean b)
319   {
320     synchronized (this)
321     {
322       redraw = b;
323     }
324   }
325 }