Skip to main content
summaryrefslogtreecommitdiffstats
blob: 18a12e791d55291ba4e1e69984e1d3108417385f (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
/****************************************************************************
 * Copyright (c) 2017, 2018 Remain Software
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *    Wim Jongman <wim.jongman@remainsoftware.com> - initial API and implementation
 *****************************************************************************/
package org.eclipse.tips.core;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.Base64;

import org.eclipse.core.runtime.Assert;
import org.eclipse.tips.core.internal.ImageUtil;

/**
 * Provides more information about the image to be used in the tip. The image
 * aspect ratio must be around 3:2 to be comfortably displayed in the Tip UI.
 *
 */
public class TipImage {

	private static final double THREE_TO_TWO = 1.5;

	/**
	 * Value to indicate that the height or width are to be determined by the Tip
	 * framework.
	 */
	public static final int UNDEFINED = -1;

	private String fExtension = null;
	private int fMaxWidth = UNDEFINED;
	private int fMaxHeight = UNDEFINED;
	final private URL fURL;
	private double fAspectRatio = THREE_TO_TWO;

	final private String fBase64Image;

	private static final int _4KB = 4096;

	/**
	 * Creates a new TipImage with the specified URL which gets read into a base 64
	 * string.
	 *
	 * @param url
	 *            the image URL which may not be null
	 * @throws IOException
	 *             in case the stream of the passed URL could not be opened or read.
	 *
	 */
	public TipImage(URL url) throws IOException {
		Assert.isNotNull(url);
		fURL = url;
		ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
		byte[] chunk = new byte[_4KB];
		int bytesRead;
		InputStream stream = url.openStream();
		while ((bytesRead = stream.read(chunk)) > 0) {
			outputStream.write(chunk, 0, bytesRead);
		}
		fBase64Image = "data:image/" //
				+ getExtension() //
				+ ";base64," //
				+ Base64.getEncoder().encodeToString(outputStream.toByteArray());
	}

	/**
	 * @return true if the URL constructor was used and not the base64 constructor.
	 */
	public boolean isURLSet() {
		return fURL != null;
	}

	/**
	 * Creates a new {@link TipImage} with the specified base64 string which must be
	 * a valid RFC-2397 string thats begins with
	 * <code>"data:image/&lt;subtype&gt;;base64"</code> where <code>subtype</code>
	 * must be a valid image type (pmg, bmp, gif, etc..).
	 *
	 * @param base64Image
	 *            the non-null base64 encoded image according to RFC-2397.
	 *
	 * @throws RuntimeException
	 *             if the string is not valid
	 * @see TipImage
	 * @see <a href="https://tools.ietf.org/search/rfc2397">RFC-2397
	 *      (https://tools.ietf.org/search/rfc2397)</a>
	 *
	 */
	public TipImage(String base64Image) {
		Assert.isNotNull(base64Image);
		fURL = null;
		if (base64Image.matches("^data:image\\/.*?;base64,.*$")) {
			fBase64Image = base64Image;
			int from = base64Image.indexOf("/") + 1;
			int to = base64Image.indexOf(";");
			setExtension(base64Image.substring(from, to).trim());
			setExtension(base64Image.substring(from, to).trim());
		} else {
			int length = base64Image.length();
			throw new RuntimeException("Wrong base64 data " + base64Image.substring(0, length < 50 ? length : 50));
		}
	}

	/**
	 * Sets the maximum height that this image can display. For example, if you have
	 * a 32x32 image the framework will blow it up to a larger size which will not
	 * work for the image and you might want to pass 64 to indicate that the image
	 * cannot be resized passed 64 pixels. If the height is not set or set to
	 * {@link #UNDEFINED}, then it is automatically resized based on aspect ratio
	 * and maximum width.
	 *
	 * @param maxHeight
	 *            the maximum height for this image or {@link #UNDEFINED}
	 * @return this
	 * @see #setAspectRatio(double)
	 * @see #setAspectRatio(int, int, boolean)
	 * @see #setMaxWidth(int)
	 */
	public TipImage setMaxHeight(int maxHeight) {
		fMaxHeight = maxHeight;
		return this;
	}

	/**
	 * Sets the maximum width that this image can display. For example, if you have
	 * a 32x32 image the framework will blow it up to a larger size which will not
	 * work for the image and you might want to pass 64 to indicate that the image
	 * cannot be resized passed 64 pixels. If the width is not set or set to
	 * {@link #UNDEFINED}, it is automatically resized based on aspect ratio and
	 * maximum height.
	 *
	 * @param maxWidth
	 *            the maximum width for this image or {@link #UNDEFINED}
	 * @return this
	 * @see #setAspectRatio(double)
	 * @see #setAspectRatio(int, int, boolean)
	 * @see #setMaxHeight(int)
	 */
	public TipImage setMaxWidth(int maxWidth) {
		fMaxWidth = maxWidth;
		return this;
	}

	/**
	 * Sets the aspect ratio of this image. If the image is 300 wide and 600 high
	 * then the aspect ratio is 300/600 = 0,5 (1:2). If your image is 1200 wide and
	 * 250 high then the aspect ratio (1200/250) = 4,8. With the supplied values the
	 * best dimensions for the image can be calculated give the available space in
	 * the UI.
	 * <p>
	 * In case you pass true for <code>pSetAsMax</code> then the image can not be
	 * up-scaled beyond the specified size. So if your image is 200x200 and you want
	 * a maximum up-scale of 2 then pass 400x400 to this method to maintain the
	 * aspect ratio but allow the image to be resized to maximum it's double size.
	 * <p>
	 * The recommended aspect ratio is around 3:2 (1.5) to be comfortably displayed
	 * in the Tip UI.
	 *
	 * @param width
	 *            the width of the image, must be greater than 0
	 * @param height
	 *            the height of the image, must be greater than 0
	 * @param setAsMax
	 *            true to set the passed width and height as the maximum width and
	 *            height for the image
	 * @return this
	 * @see #setAspectRatio(double)
	 * @see #getIMGAttributes(int, int)
	 * @see #setMaxHeight(int)
	 * @see #setMaxWidth(int)
	 */
	public TipImage setAspectRatio(int width, int height, boolean setAsMax) {
		Assert.isTrue(width > 0);
		Assert.isTrue(height > 0);
		fAspectRatio = (double) width / (double) height;
		if (setAsMax) {
			setMaxHeight(height);
			setMaxWidth(width);
		}
		return this;
	}

	/**
	 * Sets the aspect ratio of your image which is defined by width divided by
	 * height.
	 * <p>
	 * The recommended aspect ratio is around 3:2 (1.5) to be comfortably displayed
	 * in the Tip UI.
	 *
	 *
	 * @param aspectRatio
	 *            the aspect ration
	 * @return this
	 * @see #setAspectRatio(int, int, boolean)
	 * @see #getIMGAttributes(int, int)
	 */
	public TipImage setAspectRatio(double aspectRatio) {
		fAspectRatio = aspectRatio;
		return this;
	}

	/**
	 * Changes the default value "null" to the passed value which commonly is "png",
	 * "gif" and such.
	 *
	 * @param extension
	 *            the extension of this file
	 * @return this
	 * @see #getExtension()
	 */
	public TipImage setExtension(String extension) {
		fExtension = extension;
		return this;
	}

	/**
	 * Returns the base64 encoded image string according to RFC-2397 or null. The
	 * recommended aspect ratio is around 3:2.
	 *
	 * @return the base64 encoded image string according to RFC-2397 or null
	 */
	public String getBase64Image() {
		return fBase64Image;
	}

	/**
	 * Returns the width and height attributes of the HTML IMG tag.
	 *
	 * <pre>
	 *  &lt;img src="smiley.gif" height="42" width="42"&gt;
	 * </pre>
	 *
	 * The available space in the UI is passed and with it the best size of the
	 * image will be calculated based on the aspect ratio of this image.
	 *
	 * Clients may override if they can provide better information.
	 *
	 * @param widthHint
	 *            the available width which must be greater than 0
	 * @param heightHint
	 *            the available height which must be greater than 0
	 * @return the attributes in the HTML img tag
	 * @see TipImage#setAspectRatio(double)
	 * @see TipImage#setAspectRatio(int, int, boolean)
	 * @see TipImage#setMaxHeight(int)
	 * @see TipImage#setMaxWidth(int)
	 */
	public String getIMGAttributes(int widthHint, int heightHint) {

		int myWidthHint = (fMaxWidth == UNDEFINED) ? widthHint : Math.min(widthHint, fMaxWidth);
		int myHeightHint = (fMaxHeight == UNDEFINED) ? heightHint : Math.min(heightHint, fMaxHeight);

		int width = ImageUtil.getWidth(fAspectRatio, myWidthHint, myHeightHint);
		int height = ImageUtil.getHeight(fAspectRatio, myWidthHint, myHeightHint);

		String result = "";
		if (fMaxWidth == UNDEFINED) {
			result += " width=\"" + width + "\"";
		} else {
			result += " width=\"" + Math.min(fMaxWidth, width) + "\"";
		}

		if (fMaxHeight == UNDEFINED) {
			result += " height=\"" + height + "\"";
		} else {
			result += " height=\"" + Math.min(fMaxHeight, height) + "\"";
		}
		return result;
	}

	/**
	 * Returns the image extension for use in the IMG tag for the data attribute
	 * (<code>data:image/???</code>). If the extension is not set in this object,
	 * then the URL is examined to find the extension. If that can not be determined
	 * then "png" is returned.
	 *
	 * @return the extension
	 */
	private String getExtension() {
		if (fExtension != null) {
			return fExtension;
		}
		String[] split = fURL.getPath().split("\\.");
		if (split.length > 1) {
			fExtension = split[split.length - 1];
		} else {
			fExtension = "png";
		}
		return fExtension;
	}
}

Back to the top