| /**************************************************************************** |
| * 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/<subtype>;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> |
| * <img src="smiley.gif" height="42" width="42"> |
| * </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; |
| } |
| } |