8bdcfabfee341b26bea43f05fe04df70c0d78627
[fanfix.git] / src / be / nikiroo / fanfix_swing / gui / book / BookCoverImager.java
1 package be.nikiroo.fanfix_swing.gui.book;
2
3 import java.awt.Color;
4 import java.awt.Graphics;
5 import java.awt.Graphics2D;
6 import java.awt.Polygon;
7 import java.awt.Rectangle;
8 import java.awt.image.BufferedImage;
9 import java.io.ByteArrayInputStream;
10 import java.io.ByteArrayOutputStream;
11 import java.io.IOException;
12 import java.io.InputStream;
13 import java.net.MalformedURLException;
14
15 import javax.imageio.ImageIO;
16
17 import be.nikiroo.fanfix.Instance;
18 import be.nikiroo.fanfix.data.MetaData;
19 import be.nikiroo.fanfix.library.BasicLibrary;
20 import be.nikiroo.fanfix.reader.ui.GuiReaderBookInfo;
21 import be.nikiroo.utils.Image;
22 import be.nikiroo.utils.ui.ImageUtilsAwt;
23 import be.nikiroo.utils.ui.UIUtils;
24
25 /**
26 * This class can create a cover icon ready to use for the graphical
27 * application.
28 *
29 * @author niki
30 */
31 class BookCoverImager {
32 // TODO: export some of the configuration options?
33 static final int COVER_WIDTH = 100;
34 static final int COVER_HEIGHT = 150;
35 static final int SPINE_WIDTH = 5;
36 static final int SPINE_HEIGHT = 5;
37 static final int HOFFSET = 20;
38 static final Color SPINE_COLOR_BOTTOM = new Color(180, 180, 180);
39 static final Color SPINE_COLOR_RIGHT = new Color(100, 100, 100);
40 static final Color BORDER = Color.black;
41
42 public static final Color UNCACHED_ICON_COLOR = Color.green.darker();
43 // new Color(0, 80, 220);
44
45 public static final int TEXT_HEIGHT = 50;
46 public static final int TEXT_WIDTH = COVER_WIDTH + 40;
47
48 //
49
50 static public Color getBackground(boolean enabled, boolean selected, boolean hovered) {
51 Color color = new Color(255, 255, 255, 0);
52 if (!enabled) {
53 } else if (selected && !hovered) {
54 color = new Color(80, 80, 100, 40);
55 } else if (!selected && hovered) {
56 color = new Color(230, 230, 255, 100);
57 } else if (selected && hovered) {
58 color = new Color(200, 200, 255, 100);
59 }
60
61 return color;
62 }
63
64 /**
65 * Draw a partially transparent overlay if needed depending upon the selection
66 * and mouse-hover states on top of the normal component, as well as a possible
67 * "cached" icon if the item is cached.
68 *
69 * @param g the {@link Graphics} to paint onto
70 * @param enabled draw an enabled overlay
71 * @param selected draw a selected overlay
72 * @param hovered draw a hovered overlay
73 * @param cached draw a non-cached overlay if needed
74 */
75 static public void paintOverlay(Graphics g, boolean enabled, boolean selected, boolean hovered, boolean cached) {
76 Rectangle clip = g.getClipBounds();
77 if (clip.getWidth() <= 0 || clip.getHeight() <= 0) {
78 return;
79 }
80
81 int h = COVER_HEIGHT;
82 int w = COVER_WIDTH;
83 int xOffset = (TEXT_WIDTH - COVER_WIDTH) - 1;
84 int yOffset = HOFFSET;
85
86 if (BORDER != null) {
87 if (BORDER != null) {
88 g.setColor(BORDER);
89 g.drawRect(xOffset, yOffset, COVER_WIDTH, COVER_HEIGHT);
90 }
91
92 xOffset++;
93 yOffset++;
94 }
95
96 int[] xs = new int[] { xOffset, xOffset + SPINE_WIDTH, xOffset + w + SPINE_WIDTH, xOffset + w };
97 int[] ys = new int[] { yOffset + h, yOffset + h + SPINE_HEIGHT, yOffset + h + SPINE_HEIGHT, yOffset + h };
98 g.setColor(SPINE_COLOR_BOTTOM);
99 g.fillPolygon(new Polygon(xs, ys, xs.length));
100 xs = new int[] { xOffset + w, xOffset + w + SPINE_WIDTH, xOffset + w + SPINE_WIDTH, xOffset + w };
101 ys = new int[] { yOffset, yOffset + SPINE_HEIGHT, yOffset + h + SPINE_HEIGHT, yOffset + h };
102 g.setColor(SPINE_COLOR_RIGHT);
103 g.fillPolygon(new Polygon(xs, ys, xs.length));
104
105 Color color = getBackground(enabled, selected, hovered);
106
107 g.setColor(color);
108 g.fillRect(clip.x, clip.y, clip.width, clip.height);
109
110 UIUtils.drawEllipse3D(g, UNCACHED_ICON_COLOR, COVER_WIDTH + HOFFSET + 30, 10, 20, 20, cached);
111 }
112
113 /**
114 * Generate a cover icon based upon the given {@link MetaData}.
115 *
116 * @param lib the library the meta comes from
117 * @param meta the {@link MetaData}
118 *
119 * @return the image
120 */
121 static public java.awt.Image generateCoverImage(BasicLibrary lib, MetaData meta) {
122 return generateCoverImage(lib, BookInfo.fromMeta(lib, meta));
123 }
124
125 /**
126 * The width of a cover image.
127 *
128 * @return the width
129 */
130 static public int getCoverWidth() {
131 return SPINE_WIDTH + COVER_WIDTH;
132 }
133
134 /**
135 * The height of a cover image.
136 *
137 * @return the height
138 */
139 static public int getCoverHeight() {
140 return COVER_HEIGHT + HOFFSET;
141 }
142
143 /**
144 * Generate a cover icon based upon the given {@link GuiReaderBookInfo}.
145 *
146 * @param lib the library the meta comes from (can be NULL)
147 * @param info the {@link GuiReaderBookInfo}
148 *
149 * @return the image
150 */
151 static public java.awt.Image generateCoverImage(BasicLibrary lib, BookInfo info) {
152 BufferedImage resizedImage = null;
153 String id = getIconId(info);
154
155 InputStream in = Instance.getInstance().getCache().getFromCache(id);
156 if (in != null) {
157 try {
158 resizedImage = ImageUtilsAwt.fromImage(new Image(in));
159 in.close();
160 in = null;
161 } catch (IOException e) {
162 Instance.getInstance().getTraceHandler().error(e);
163 }
164 }
165
166 if (resizedImage == null) {
167 try {
168 Image cover = null;
169 if (info != null) {
170 cover = info.getBaseImage(lib);
171 }
172
173 resizedImage = new BufferedImage(getCoverWidth(), getCoverHeight(), BufferedImage.TYPE_4BYTE_ABGR);
174
175 Graphics2D g = resizedImage.createGraphics();
176 try {
177 g.setColor(Color.white);
178 g.fillRect(0, HOFFSET, COVER_WIDTH, COVER_HEIGHT);
179
180 if (cover != null) {
181 BufferedImage coverb = ImageUtilsAwt.fromImage(cover);
182 g.drawImage(coverb, 0, HOFFSET, COVER_WIDTH, COVER_HEIGHT, null);
183 } else {
184 g.setColor(Color.black);
185 g.drawLine(0, HOFFSET, COVER_WIDTH, HOFFSET + COVER_HEIGHT);
186 g.drawLine(COVER_WIDTH, HOFFSET, 0, HOFFSET + COVER_HEIGHT);
187 }
188 } finally {
189 g.dispose();
190 }
191
192 // Only save image with a cover, not the X thing
193 if (id != null && cover != null) {
194 ByteArrayOutputStream out = new ByteArrayOutputStream();
195 ImageIO.write(resizedImage, "png", out);
196 byte[] imageBytes = out.toByteArray();
197 in = new ByteArrayInputStream(imageBytes);
198 Instance.getInstance().getCache().addToCache(in, id);
199 in.close();
200 in = null;
201 }
202 } catch (MalformedURLException e) {
203 Instance.getInstance().getTraceHandler().error(e);
204 } catch (IOException e) {
205 Instance.getInstance().getTraceHandler().error(e);
206 }
207 }
208
209 return resizedImage;
210 }
211
212 /**
213 * Manually clear the icon set for this item.
214 *
215 * @param info the info about the story or source/type or author
216 */
217 static public void clearIcon(BookInfo info) {
218 String id = getIconId(info);
219 Instance.getInstance().getCache().removeFromCache(id);
220 }
221
222 /**
223 * Get a unique ID from this {@link GuiReaderBookInfo} (note that it can be a
224 * story, a fake item for a source/type or a fake item for an author).
225 *
226 * @param info the info or NULL for a generic (non unique!) ID
227 * @return the unique ID
228 */
229 static private String getIconId(BookInfo info) {
230 return (info == null ? "" : info.getId() + ".") + "book-thumb_" + SPINE_WIDTH + "x" + COVER_WIDTH + "+"
231 + SPINE_HEIGHT + "+" + COVER_HEIGHT + "@" + HOFFSET;
232 }
233 }