Merge remote-tracking branch 'origin/feature/2588' into feature/JAL-2527
[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.viewmodel.OverviewDimensions;
33
34 import java.awt.AlphaComposite;
35 import java.awt.Color;
36 import java.awt.Graphics;
37 import java.awt.Graphics2D;
38 import java.awt.image.BufferedImage;
39
40 public class OverviewRenderer
41 {
42   // transparency of hidden cols/seqs overlay
43   private final float TRANSPARENCY = 0.5f;
44
45   private FeatureColourFinder finder;
46
47   // image to render on
48   private BufferedImage miniMe;
49
50   // raw number of pixels to allocate to each column
51   private float pixelsPerCol;
52
53   // raw number of pixels to allocate to each row
54   private float pixelsPerSeq;
55
56   // flag to indicate whether to halt drawing
57   private volatile boolean redraw = false;
58
59   // reference to alignment, needed to get sequence groups
60   private AlignmentI al;
61
62   private ResidueShaderI shader;
63
64   private ResidueColourFinder resColFinder;
65
66   public OverviewRenderer(FeatureRenderer fr, OverviewDimensions od,
67           AlignmentI alignment,
68           ResidueShaderI resshader)
69   {
70     finder = new FeatureColourFinder(fr);
71     resColFinder = new OverviewResColourFinder();
72
73     al = alignment;
74     shader = resshader;
75
76     pixelsPerCol = od.getPixelsPerCol();
77     pixelsPerSeq = od.getPixelsPerSeq();
78     miniMe = new BufferedImage(od.getWidth(), od.getHeight(),
79             BufferedImage.TYPE_INT_RGB);
80   }
81
82   /**
83    * Draw alignment rows and columns onto an image
84    * 
85    * @param rit
86    *          Iterator over rows to be drawn
87    * @param cit
88    *          Iterator over columns to be drawn
89    * @return image containing the drawing
90    */
91   public BufferedImage draw(AlignmentRowsCollectionI rows,
92           AlignmentColsCollectionI cols)
93   {
94     int rgbcolor = Color.white.getRGB();
95     int seqIndex = 0;
96     int pixelRow = 0;
97
98     for (int alignmentRow : rows)
99     {
100       if (redraw)
101       {
102         break;
103       }
104     
105       // get details of this alignment row
106       SequenceI seq = rows.getSequence(alignmentRow);
107
108       // rate limiting step when rendering overview for lots of groups
109       SequenceGroup[] allGroups = al.findAllGroups(seq);
110
111       // calculate where this row extends to in pixels
112       int endRow = Math.min(Math.round((seqIndex + 1) * pixelsPerSeq) - 1,
113               miniMe.getHeight() - 1);
114     
115       int colIndex = 0;
116       int pixelCol = 0;
117       for (int alignmentCol : cols)
118       {
119         if (redraw)
120         {
121           break;
122         }
123     
124         // calculate where this column extends to in pixels
125         int endCol = Math.min(Math.round((colIndex + 1) * pixelsPerCol) - 1,
126                 miniMe.getWidth() - 1);
127     
128         // don't do expensive colour determination if we're not going to use it
129         // NB this is important to avoid performance issues in the overview
130         // panel
131         if (pixelCol <= endCol)
132         {
133           rgbcolor = getColumnColourFromSequence(allGroups, seq,
134                   alignmentCol, finder);
135     
136           // fill in the appropriate number of pixels
137           for (int row = pixelRow; row <= endRow; ++row)
138           {
139             for (int col = pixelCol; col <= endCol; ++col)
140             {
141               miniMe.setRGB(col, row, rgbcolor);
142             }
143           }
144     
145           pixelCol = endCol + 1;
146         }
147         colIndex++;
148       }
149       pixelRow = endRow + 1;
150       seqIndex++;
151     }
152
153     overlayHiddenRegions(rows, cols);
154     return miniMe;
155   }
156
157   /**
158    * Find the colour of a sequence at a specified column position
159    * 
160    * @param seq
161    *          sequence to get colour for
162    * @param lastcol
163    *          column position to get colour for
164    * @param fcfinder
165    *          FeatureColourFinder to use
166    * @return colour of sequence at this position, as RGB
167    */
168   private int getColumnColourFromSequence(SequenceGroup[] allGroups,
169           jalview.datamodel.SequenceI seq,
170           int lastcol, FeatureColourFinder fcfinder)
171   {
172     Color color = Color.white;
173
174     if ((seq != null) && (seq.getLength() > lastcol))
175     {
176       color = resColFinder.getResidueColour(true, shader, allGroups, seq,
177               lastcol,
178               fcfinder);
179     }
180
181     return color.getRGB();
182   }
183
184   /**
185    * Overlay the hidden regions on the overview image
186    * 
187    * @param rows
188    *          collection of rows the overview is built over
189    * @param cols
190    *          collection of columns the overview is built over
191    */
192   private void overlayHiddenRegions(AlignmentRowsCollectionI rows,
193           AlignmentColsCollectionI cols)
194   {
195     if (cols.hasHidden() || rows.hasHidden())
196     {
197       BufferedImage mask = buildHiddenImage(rows, cols, miniMe.getWidth(),
198               miniMe.getHeight());
199
200       Graphics2D g = (Graphics2D) miniMe.getGraphics();
201       g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
202               TRANSPARENCY));
203       g.drawImage(mask, 0, 0, miniMe.getWidth(), miniMe.getHeight(), null);
204     }
205   }
206
207   /**
208    * Build a masking image of hidden columns and rows to be applied on top of
209    * the main overview image.
210    * 
211    * @param rows
212    *          collection of rows the overview is built over
213    * @param cols
214    *          collection of columns the overview is built over
215    * @param width
216    *          width of overview in pixels
217    * @param height
218    *          height of overview in pixels
219    * @return BufferedImage containing mask of hidden regions
220    */
221   private BufferedImage buildHiddenImage(AlignmentRowsCollectionI rows,
222           AlignmentColsCollectionI cols, int width, int height)
223   {
224     // new masking image
225     BufferedImage hiddenImage = new BufferedImage(width, height,
226             BufferedImage.TYPE_INT_ARGB);
227
228     int colIndex = 0;
229     int pixelCol = 0;
230
231     Color hidden = Color.DARK_GRAY.darker();
232
233     Graphics2D g2d = (Graphics2D) hiddenImage.getGraphics();
234
235     // set background to transparent
236     g2d.setComposite(AlphaComposite.Clear);
237     g2d.fillRect(0, 0, width, height);
238
239     // set next colour to opaque
240     g2d.setComposite(AlphaComposite.Src);
241
242     for (int alignmentCol : cols)
243     {
244       if (redraw)
245       {
246         break;
247       }
248
249       // calculate where this column extends to in pixels
250       int endCol = Math.min(Math.round((colIndex + 1) * pixelsPerCol) - 1,
251               hiddenImage.getWidth() - 1);
252
253       if (pixelCol <= endCol)
254       {
255         // determine the colour based on the sequence and column position
256         if (cols.isHidden(alignmentCol))
257         {
258           g2d.setColor(hidden);
259           g2d.fillRect(pixelCol, 0, endCol - pixelCol + 1, height);
260         }
261
262         pixelCol = endCol + 1;
263       }
264       colIndex++;
265
266     }
267
268     int seqIndex = 0;
269     int pixelRow = 0;
270     for (int alignmentRow : rows)
271     {
272       if (redraw)
273       {
274         break;
275       }
276
277       // calculate where this row extends to in pixels
278       int endRow = Math.min(Math.round((seqIndex + 1) * pixelsPerSeq) - 1,
279               miniMe.getHeight() - 1);
280
281       // get details of this alignment row
282       if (rows.isHidden(alignmentRow))
283       {
284         g2d.setColor(hidden);
285         g2d.fillRect(0, pixelRow, width, endRow - pixelRow + 1);
286       }
287       pixelRow = endRow + 1;
288       seqIndex++;
289     }
290
291     return hiddenImage;
292   }
293
294   /**
295    * Draw the alignment annotation in the overview panel
296    * 
297    * @param g
298    *          the graphics object to draw on
299    * @param anno
300    *          alignment annotation information
301    * @param charWidth
302    *          alignment character width value
303    * @param y
304    *          y-position for the annotation graph
305    * @param cols
306    *          the collection of columns used in the overview panel
307    */
308   public void drawGraph(Graphics g, AlignmentAnnotation anno, int charWidth,
309           int y, AlignmentColsCollectionI cols)
310   {
311     Annotation[] annotations = anno.annotations;
312     g.setColor(Color.white);
313     g.fillRect(0, 0, miniMe.getWidth(), y);
314
315     int height;
316     int colIndex = 0;
317     int pixelCol = 0;
318     for (int alignmentCol : cols)
319     {
320       if (redraw)
321       {
322         break;
323       }
324       if (alignmentCol >= annotations.length)
325       {
326         break; // no more annotations to draw here
327       }
328       else
329       {
330         int endCol = Math.min(Math.round((colIndex + 1) * pixelsPerCol) - 1,
331                 miniMe.getWidth() - 1);
332
333         if (annotations[alignmentCol] != null)
334         {
335           if (annotations[alignmentCol].colour == null)
336           {
337             g.setColor(Color.black);
338           }
339           else
340           {
341             g.setColor(annotations[alignmentCol].colour);
342           }
343
344           height = (int) ((annotations[alignmentCol].value / anno.graphMax)
345                   * y);
346           if (height > y)
347           {
348             height = y;
349           }
350
351           g.fillRect(pixelCol, y - height, endCol - pixelCol + 1, height);
352         }
353         pixelCol = endCol + 1;
354         colIndex++;
355       }
356     }
357   }
358
359   /**
360    * Allows redraw flag to be set
361    * 
362    * @param b
363    *          value to set redraw to: true = redraw is occurring, false = no
364    *          redraw
365    */
366   public void setRedraw(boolean b)
367   {
368     synchronized (this)
369     {
370       redraw = b;
371     }
372   }
373 }