48dcf3cdf96f89dacdb0bc024adcfed5e8d99cf9
[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   private FeatureColourFinder finder;
41
42   private jalview.api.SequenceRenderer sr;
43
44   // image to render on
45   private BufferedImage miniMe;
46
47   // raw number of pixels to allocate to each column
48   private float pixelsPerCol;
49
50   // raw number of pixels to allocate to each row
51   private float pixelsPerSeq;
52
53   // flag to indicate whether to halt drawing
54   private volatile boolean redraw = false;
55
56   public OverviewRenderer(jalview.api.SequenceRenderer seqRenderer,
57           FeatureRenderer fr, OverviewDimensions od)
58   {
59     sr = seqRenderer;
60     finder = new FeatureColourFinder(fr);
61
62     pixelsPerCol = od.getPixelsPerCol();
63     pixelsPerSeq = od.getPixelsPerSeq();
64     miniMe = new BufferedImage(od.getWidth(), od.getHeight(),
65             BufferedImage.TYPE_INT_RGB);
66   }
67
68   /**
69    * Draw alignment rows and columns onto an image
70    * 
71    * @param rit
72    *          Iterator over rows to be drawn
73    * @param cit
74    *          Iterator over columns to be drawn
75    * @return image containing the drawing
76    */
77   public BufferedImage draw(AlignmentRowsCollectionI rows,
78           AlignmentColsCollectionI cols)
79   {
80     int rgbcolor = Color.white.getRGB();
81     int seqIndex = 0;
82     int pixelRow = 0;
83
84     BufferedImage hiddenImage = buildHiddenImage(rows, cols,
85             miniMe.getWidth(), miniMe.getHeight());
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     return applyMask(hiddenImage, miniMe);
142   }
143
144   /*
145    * Find the colour of a sequence at a specified column position
146    */
147   private int getColumnColourFromSequence(jalview.datamodel.SequenceI seq,
148           int lastcol, FeatureColourFinder fcfinder)
149   {
150     Color color = Color.white;
151
152     if ((seq != null) && (seq.getLength() > lastcol))
153     {
154       color = sr.getResidueColour(seq, lastcol, fcfinder);
155     }
156
157     return color.getRGB();
158   }
159
160   private BufferedImage applyMask(BufferedImage mask, BufferedImage image)
161   {
162     Graphics2D g = (Graphics2D) image.getGraphics();
163     g.setComposite(
164             AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.5f));
165     g.drawImage(mask, 0, 0, image.getWidth(), image.getHeight(), null);
166     return image;
167   }
168
169   /*
170    * Build a masking image of hidden columns and rows to be applied on top
171    * of the main overview image.
172    */
173   private BufferedImage buildHiddenImage(AlignmentRowsCollectionI rows,
174           AlignmentColsCollectionI cols, int width, int height)
175   {
176     // new masking image
177     BufferedImage hiddenImage = new BufferedImage(width, height,
178             BufferedImage.TYPE_INT_ARGB);
179
180     int colIndex = 0;
181     int pixelCol = 0;
182
183     Color hidden = Color.DARK_GRAY;
184
185     Graphics2D g2d = (Graphics2D) hiddenImage.getGraphics();
186
187     // set background to transparent
188     g2d.setComposite(AlphaComposite.Clear);
189     g2d.fillRect(0, 0, width, height);
190
191     // set next colour to opaque
192     g2d.setComposite(AlphaComposite.Src);
193
194     for (int alignmentCol : cols)
195     {
196       if (redraw)
197       {
198         break;
199       }
200
201       // calculate where this column extends to in pixels
202       int endCol = Math.min(Math.round((colIndex + 1) * pixelsPerCol) - 1,
203               hiddenImage.getWidth() - 1);
204
205       if (pixelCol <= endCol)
206       {
207         // determine the colour based on the sequence and column position
208         if (cols.isHidden(alignmentCol))
209         {
210           g2d.setColor(hidden);
211           g2d.fillRect(pixelCol, 0, endCol - pixelCol + 1, height);
212         }
213
214         pixelCol = endCol + 1;
215       }
216       colIndex++;
217
218     }
219
220     int seqIndex = 0;
221     int pixelRow = 0;
222     for (int alignmentRow : rows)
223     {
224       if (redraw)
225       {
226         break;
227       }
228
229       // calculate where this row extends to in pixels
230       int endRow = Math.min(Math.round((seqIndex + 1) * pixelsPerSeq) - 1,
231               miniMe.getHeight() - 1);
232
233       // get details of this alignment row
234       if (rows.isHidden(alignmentRow))
235       {
236         g2d.setColor(hidden);
237         g2d.fillRect(0, pixelRow, width, endRow - pixelRow + 1);
238       }
239       pixelRow = endRow + 1;
240       seqIndex++;
241     }
242
243     return hiddenImage;
244   }
245
246   /**
247    * Draw the alignment annotation in the overview panel
248    * 
249    * @param g
250    *          the graphics object to draw on
251    * @param anno
252    *          alignment annotation information
253    * @param charWidth
254    *          alignment character width value
255    * @param y
256    *          y-position for the annotation graph
257    * @param cols
258    *          the collection of columns used in the overview panel
259    */
260   public void drawGraph(Graphics g, AlignmentAnnotation anno, int charWidth,
261           int y, AlignmentColsCollectionI cols)
262   {
263     Annotation[] annotations = anno.annotations;
264     g.setColor(Color.white);
265     g.fillRect(0, 0, miniMe.getWidth(), y);
266
267     int height;
268     int colIndex = 0;
269     int pixelCol = 0;
270     for (int alignmentCol : cols)
271     {
272       if (redraw)
273       {
274         break;
275       }
276       if (alignmentCol >= annotations.length)
277       {
278         break; // no more annotations to draw here
279       }
280       else
281       {
282         int endCol = Math.min(
283                 Math.round((colIndex + 1) * pixelsPerCol) - 1,
284                 miniMe.getWidth() - 1);
285
286         if (annotations[alignmentCol] != null)
287         {
288           if (annotations[alignmentCol].colour == null)
289           {
290             g.setColor(Color.black);
291           }
292           else
293           {
294             g.setColor(annotations[alignmentCol].colour);
295           }
296
297           height = (int) ((annotations[alignmentCol].value / anno.graphMax) * y);
298           if (height > y)
299           {
300             height = y;
301           }
302
303           g.fillRect(pixelCol, y - height, endCol - pixelCol + 1, height);
304         }
305         pixelCol = endCol + 1;
306         colIndex++;
307       }
308     }
309   }
310
311   public void setRedraw(boolean b)
312   {
313     synchronized (this)
314     {
315       redraw = b;
316     }
317   }
318 }