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