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