Merge branch 'releases/Release_2_11_3_Branch'
[jalview.git] / src / jalview / util / ImageMaker.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.util;
22
23 import java.awt.Graphics;
24 import java.awt.Graphics2D;
25 import java.awt.RenderingHints;
26 import java.awt.image.BufferedImage;
27 import java.io.File;
28 import java.io.FileOutputStream;
29 import java.io.IOException;
30
31 import javax.imageio.ImageIO;
32
33 import org.jfree.graphics2d.svg.SVGGraphics2D;
34 import org.jfree.graphics2d.svg.SVGHints;
35 import org.jibble.epsgraphics.EpsGraphics2D;
36
37 import jalview.bin.Console;
38 import jalview.io.JalviewFileChooser;
39 import jalview.util.imagemaker.BitmapImageSizing;
40
41 public class ImageMaker
42 {
43   public static final String SVG_DESCRIPTION = "Scalable Vector Graphics";
44
45   public static final String SVG_EXTENSION = "svg";
46
47   public static final String EPS_DESCRIPTION = "Encapsulated Postscript";
48
49   public static final String EPS_EXTENSION = "eps";
50
51   public static final String PNG_EXTENSION = "png";
52
53   public static final String PNG_DESCRIPTION = "Portable  network graphics";
54
55   EpsGraphics2D pg;
56
57   Graphics graphics;
58
59   FileOutputStream out;
60
61   BufferedImage bi;
62
63   TYPE type;
64
65   public enum TYPE
66   {
67     EPS("EPS", MessageManager.getString("label.eps_file"), EPS_EXTENSION,
68             EPS_DESCRIPTION),
69     PNG("PNG", MessageManager.getString("label.png_image"), PNG_EXTENSION,
70             PNG_DESCRIPTION),
71     SVG("SVG", "SVG", SVG_EXTENSION, SVG_DESCRIPTION);
72
73     public final String name;
74
75     public final String label;
76
77     public final String extension;
78
79     public final String description;
80
81     TYPE(String name, String label, String ext, String desc)
82     {
83       this.name = name;
84       this.label = label;
85       this.extension = ext;
86       this.description = desc;
87     }
88
89     public String getName()
90     {
91       return name;
92     }
93
94     public JalviewFileChooser getFileChooser()
95     {
96       return new JalviewFileChooser(extension, description);
97     }
98
99     public String getLabel()
100     {
101       return label;
102     }
103
104   }
105
106   /**
107    * Constructor configures the graphics context ready for writing to
108    * 
109    * @param imageType
110    * @param width
111    * @param height
112    * @param file
113    * @param fileTitle
114    * @param useLineart
115    * @param bitmapscale
116    * @throws IOException
117    */
118   public ImageMaker(TYPE imageType, int width, int height, File file,
119           String fileTitle, boolean useLineart, BitmapImageSizing userBis)
120           throws IOException
121   {
122     this.type = imageType;
123
124     out = new FileOutputStream(file);
125     switch (imageType)
126     {
127     case SVG:
128       setupSVG(width, height, useLineart);
129       break;
130     case EPS:
131       setupEPS(width, height, fileTitle, useLineart);
132       break;
133     case PNG:
134       setupPNG(width, height, userBis);
135       break;
136     default:
137     }
138   }
139
140   public Graphics getGraphics()
141   {
142     return graphics;
143   }
144
145   /**
146    * For SVG or PNG, writes the generated graphics data to the file output
147    * stream. For EPS, flushes the output graphics (which is written to file as
148    * it is generated).
149    */
150   public void writeImage()
151   {
152     try
153     {
154       switch (type)
155       {
156       case EPS:
157         pg.flush();
158         pg.close();
159         break;
160       case SVG:
161         String svgData = ((SVGGraphics2D) getGraphics()).getSVGDocument();
162         out.write(svgData.getBytes());
163         out.flush();
164         out.close();
165         break;
166       case PNG:
167         ImageIO.write(bi, PNG_EXTENSION, out);
168         out.flush();
169         out.close();
170         break;
171       }
172     } catch (Exception ex)
173     {
174       ex.printStackTrace();
175     }
176   }
177
178   /**
179    * Sets up a graphics object for the PNG image to be written on
180    * 
181    * @param width
182    * @param height
183    * @param scale
184    */
185   protected void setupPNG(int width, int height, BitmapImageSizing userBis)
186   {
187     if (width == 0 || height == 0)
188       return;
189
190     BitmapImageSizing bis = ImageMaker.getScaleWidthHeight(width, height,
191             userBis);
192     float usescale = bis.scale();
193     int usewidth = bis.width();
194     int useheight = bis.height();
195
196     bi = new BufferedImage(usewidth, useheight, BufferedImage.TYPE_INT_RGB);
197     graphics = bi.getGraphics();
198     Graphics2D ig2 = (Graphics2D) graphics;
199     ig2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
200             RenderingHints.VALUE_ANTIALIAS_ON);
201     if (usescale > 0.0f)
202     {
203       ig2.scale(usescale, usescale);
204     }
205   }
206
207   /**
208    * A helper method to configure the SVG output graphics, with choice of Text
209    * or Lineart character rendering
210    * 
211    * @param width
212    * @param height
213    * @param useLineart
214    *          true for Lineart character rendering, false for Text
215    */
216   protected void setupSVG(int width, int height, boolean useLineart)
217   {
218     SVGGraphics2D g2 = new SVGGraphics2D(width, height);
219     if (useLineart)
220     {
221       g2.setRenderingHint(SVGHints.KEY_DRAW_STRING_TYPE,
222               SVGHints.VALUE_DRAW_STRING_TYPE_VECTOR);
223     }
224     graphics = g2;
225   }
226
227   /**
228    * A helper method that sets up the EPS graphics output with user choice of
229    * Text or Lineart character rendering
230    * 
231    * @param width
232    * @param height
233    * @param title
234    * @param useLineart
235    *          true for Lineart character rendering, false for Text
236    * @throws IOException
237    */
238   protected void setupEPS(int width, int height, String title,
239           boolean useLineart) throws IOException
240   {
241     pg = new EpsGraphics2D(title, out, 0, 0, width, height);
242     Graphics2D ig2 = pg;
243     ig2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
244             RenderingHints.VALUE_ANTIALIAS_ON);
245     pg.setAccurateTextMode(useLineart);
246     graphics = pg;
247   }
248
249   /**
250    * Takes initial width and height, and suggested float scale, int width, int
251    * height and create a bounding box returned as a BitmapImageSizing object
252    * with consistent scale, width, height fields.
253    * 
254    * @param width
255    *          The unscaled image width
256    * @param height
257    *          The unscaled image height
258    * @param scale
259    *          The suggested scaling
260    * @param bitmapwidth
261    *          The suggested width
262    * @param bitmapheight
263    *          The suggested height
264    * @return BitmapImageSizing A consistent scale,width and height for the final
265    *         image
266    */
267   public static BitmapImageSizing getScaleWidthHeight(int width, int height,
268           float scale, int bitmapwidth, int bitmapheight)
269   {
270     float usescale = 0.0f;
271     int usewidth = width;
272     int useheight = height;
273
274     if ((width == 0 && bitmapwidth > 0)
275             || (height == 0 && bitmapheight > 0))
276     {
277       // original image is zero sized! Avoid dividing by zero!
278       return BitmapImageSizing.nullBitmapImageSizing();
279     }
280
281     // use the smallest positive scale (i.e. fit in the box)
282     if (scale > 0.0f)
283     {
284       usescale = scale;
285       usewidth = Math.round(scale * width);
286       useheight = Math.round(scale * height);
287     }
288     if (bitmapwidth > 0)
289     {
290       float wscale = (float) bitmapwidth / width;
291       if (wscale > 0.0f && (usescale == 0.0f || wscale < usescale))
292       {
293         usescale = wscale;
294         usewidth = bitmapwidth;
295         useheight = Math.round(usescale * height);
296       }
297     }
298     if (bitmapheight > 0)
299     {
300       float hscale = (float) bitmapheight / height;
301       if (hscale > 0.0f && (usescale == 0.0f || hscale < usescale))
302       {
303         usescale = hscale;
304         usewidth = Math.round(usescale * width);
305         useheight = bitmapheight;
306       }
307     }
308     return new BitmapImageSizing(usescale, usewidth, useheight, false);
309   }
310
311   /**
312    * Takes suggested scale, width, height as a BitmapImageSizing object and
313    * create a bounding box returned as a BitmapImageSizing object with
314    * consistent scale, width, height fields.
315    * 
316    * @param bis
317    * @return BitmapImageSizing
318    */
319   public static BitmapImageSizing getScaleWidthHeight(int width, int height,
320           BitmapImageSizing bis)
321   {
322     return ImageMaker.getScaleWidthHeight(width, height, bis.scale(),
323             bis.width(), bis.height());
324   }
325
326   /**
327    * Takes String versions of suggested float scale, int width, int height and
328    * create a bounding box returned as a BitmapImageSizing object with
329    * consistent scale, width, height fields.
330    * 
331    * @param scaleS
332    * @param widthS
333    * @param heightS
334    * @return BitmapImageSizing
335    */
336   public static BitmapImageSizing parseScaleWidthHeightStrings(
337           String scaleS, String widthS, String heightS)
338   {
339     if (scaleS == null && widthS == null && heightS == null)
340     {
341       // if all items are null (i.e. not provided) we use the dynamic
342       // preferences set BIS
343       return BitmapImageSizing.defaultBitmapImageSizing();
344     }
345
346     float scale = 0.0f;
347     int width = 0;
348     int height = 0;
349
350     if (scaleS != null)
351     {
352       try
353       {
354         scale = Float.parseFloat(scaleS);
355       } catch (NumberFormatException e)
356       {
357         Console.warn("Did not understand scale '" + scaleS
358                 + "', won't be used.");
359       }
360     }
361     if (widthS != null)
362     {
363       try
364       {
365         width = Integer.parseInt(widthS);
366       } catch (NumberFormatException e)
367       {
368         Console.warn("Did not understand width '" + widthS
369                 + "', won't be used.");
370       }
371     }
372     if (heightS != null)
373     {
374       try
375       {
376         height = Integer.parseInt(heightS);
377       } catch (NumberFormatException e)
378       {
379         Console.warn("Did not understand height '" + heightS
380                 + "', won't be used.");
381       }
382     }
383
384     return new BitmapImageSizing(scale, width, height, false);
385   }
386 }