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