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