diff --git a/plugins-dev/videoreader/src/java/pt/lsts/neptus/plugins/videoreader/VideoReader.java b/plugins-dev/videoreader/src/java/pt/lsts/neptus/plugins/videoreader/VideoReader.java index e2afe8d546..f32acd9cbb 100644 --- a/plugins-dev/videoreader/src/java/pt/lsts/neptus/plugins/videoreader/VideoReader.java +++ b/plugins-dev/videoreader/src/java/pt/lsts/neptus/plugins/videoreader/VideoReader.java @@ -32,9 +32,13 @@ */ package pt.lsts.neptus.plugins.videoreader; +import com.google.common.eventbus.Subscribe; +import pt.lsts.imc.EstimatedState; +import pt.lsts.imc.FuelLevel; import pt.lsts.neptus.NeptusLog; import pt.lsts.neptus.console.ConsoleLayout; import pt.lsts.neptus.console.ConsolePanel; +import pt.lsts.neptus.console.events.ConsoleEventMainSystemChange; import pt.lsts.neptus.console.notifications.Notification; import pt.lsts.neptus.i18n.I18n; import pt.lsts.neptus.plugins.NeptusProperty; @@ -42,8 +46,14 @@ import pt.lsts.neptus.plugins.PluginUtils; import pt.lsts.neptus.plugins.Popup; import pt.lsts.neptus.plugins.update.Periodic; +import pt.lsts.neptus.types.coord.CoordinateUtil; +import pt.lsts.neptus.types.coord.LocationType; import pt.lsts.neptus.util.ImageUtils; +import pt.lsts.neptus.util.RenderStringUtils; +import pt.lsts.neptus.util.conf.GeneralPreferences; +import pt.lsts.neptus.util.conf.PreferencesListener; +import javax.swing.JCheckBoxMenuItem; import javax.swing.JDialog; import javax.swing.JLabel; import javax.swing.JMenuItem; @@ -51,11 +61,16 @@ import javax.swing.KeyStroke; import javax.swing.SwingConstants; import javax.swing.SwingUtilities; +import java.awt.AlphaComposite; import java.awt.Color; import java.awt.Component; import java.awt.Dimension; +import java.awt.Font; +import java.awt.FontMetrics; import java.awt.Graphics; +import java.awt.Graphics2D; import java.awt.Image; +import java.awt.RenderingHints; import java.awt.Toolkit; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; @@ -74,11 +89,16 @@ import java.util.concurrent.ThreadFactory; import java.util.concurrent.atomic.AtomicInteger; +import static pt.lsts.neptus.util.AngleUtils.nomalizeAngleDegrees180; +import static pt.lsts.neptus.util.AngleUtils.nomalizeAngleDegrees360; +import static pt.lsts.neptus.util.ImageUtils.getImage; +import static pt.lsts.neptus.util.ImageUtils.getScaledImage; + @PluginDescription(name = "Video Reader", version = "0.1", experimental = true, author = "Paulo Dias", description = "Plugin to view IP Camera streams using FFMPEG", icon = "images/menus/camera.png", category = PluginDescription.CATEGORY.INTERFACE) @Popup(name = "Video Reader", width = 640, height = 480, icon = "images/menus/camera.png") -public class VideoReader extends ConsolePanel { +public class VideoReader extends ConsolePanel implements PreferencesListener { static final String BASE_FOLDER_FOR_URL_INI = "ipUrl.ini"; private static final int DEFAULT_WIDTH_CONSOLE = 640; @@ -147,6 +167,45 @@ public Thread newThread(Runnable r) { private final JLabel streamNameJLabel; private final JLabel streamWarnJLabel; + private double lastAspectRatio = (double) widthConsole /heightConsole; + + // JLabel for additional information + private String positionLabel = ""; + private String rpyLabel = ""; + private String velLabel = ""; + private String depthLabel = ""; + private String altitudeLabel = ""; + private String fuelLabel = ""; + + private final Image fuelFullImage = getImage("images/full_battery_video.png"); + private final Image fuelAboveHalfImage = getImage("images/above_half_battery_video.png"); + private final Image fuelBelowHalfImage = getImage("images/below_half_battery_video.png"); + private final Image fuelLowImage = getImage("images/low_battery_video.png"); + private final Image fuelEmptyImage = getImage("images/no_battery_video.png"); + private Image fuelImage = getImage("images/no_battery_video.png"); + private Image fuelIcon = getScaledImage(fuelImage, 18, 18, false); + private final Image positionImage = getImage("images/position_video.png"); + private Image positionIcon = getScaledImage(positionImage, 18, 18, false); + private final Image rpyImage = getImage("images/rpy_video.png"); + private Image rpyIcon = getScaledImage(rpyImage, 18, 18, false); + private final Image velImage = getImage("images/speed_video.png"); + private Image velIcon = getScaledImage(velImage, 18, 18, false); + private final Image depthImage = getImage("images/depth_video.png"); + private Image depthIcon = getScaledImage(depthImage, 18, 18, false); + private final Image altitudeImage = getImage("images/altitude_video.png"); + private Image altitudeIcon = getScaledImage(altitudeImage, 18, 18, false); + + private Image lastFuelImage = fuelImage; + + private int infoFontSize = 14; + + private JMenuItem showInfoItem; + + private boolean showVehicleInfo = false; + + private double positionLatDeg = Double.NaN; + private double positionLonDeg = Double.NaN; + public VideoReader(ConsoleLayout console) { this(console, false); } @@ -155,6 +214,9 @@ public VideoReader(ConsoleLayout console, boolean usedInsideAnotherConsolePanel) super(console, usedInsideAnotherConsolePanel); removeAll(); + + initPopupMenu(); + this.addComponentListener(new ComponentAdapter() { @Override public void componentResized(ComponentEvent evt) { @@ -205,16 +267,19 @@ private void updateSize(ComponentEvent evt) { streamWarnJLabel.setHorizontalAlignment(SwingConstants.CENTER); streamWarnJLabel.setVerticalAlignment(SwingConstants.BOTTOM); streamWarnJLabel.setVerticalTextPosition(SwingConstants.BOTTOM); + streamWarnJLabel.setText("⚠"); } @Override public void initSubPanel() { + GeneralPreferences.addPreferencesListener(this); service.execute(Util::createIpUrlFile); //setMainVehicle(getConsole().getMainSystem()); } @Override public void cleanSubPanel() { + GeneralPreferences.removePreferencesListener(this); closingPanel = true; service.shutdown(); disconnectStream(); @@ -244,15 +309,107 @@ else if (onScreenImageLastGood != null && (onScreenImageLastGood.getWidth() == w streamNameJLabel.setSize((int) widthConsole, (int) bounds.getHeight() + 5); streamNameJLabel.paint(g); + int x = 10; + int y = heightConsole; + int iconSpacing = 5; + double scaleFactor = (double) (Math.min(widthConsole, heightConsole)) / ((double) (DEFAULT_WIDTH_CONSOLE + DEFAULT_HEIGHT_CONSOLE) / 2); + int fontSize = validateFontSize(g, (int) (14 * scaleFactor), positionLabel); + int iconSize = fontSize + 8; + Font font = new Font("Arial", Font.PLAIN, fontSize); + int lineHeight = (int) (fontSize * 2); + + if (showVehicleInfo) { + double aspectRatio = (double) widthConsole / heightConsole; + if (lastAspectRatio != aspectRatio) { + lastAspectRatio = aspectRatio; + + fuelIcon = getScaledImage(fuelImage, iconSize, iconSize, false); + rpyIcon = getScaledImage(rpyImage, iconSize, iconSize, false); + velIcon = getScaledImage(velImage, iconSize, iconSize, false); + depthIcon = getScaledImage(depthImage, iconSize, iconSize, false); + altitudeIcon = getScaledImage(altitudeImage, iconSize, iconSize, false); + positionIcon = getScaledImage(positionImage, iconSize, iconSize, false); + } + + if (lastFuelImage != fuelImage) { + lastFuelImage = fuelImage; + fuelIcon = getScaledImage(fuelImage, iconSize, iconSize, false); + } + + Graphics2D g2d = (Graphics2D) g; + g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + AlphaComposite composite = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.7f); // 50% opacity + g2d.setComposite(composite); + g2d.setColor(Color.BLACK); + + int positionLabelWidth = getLabelWidth(g, fontSize, positionLabel); + int leftPanelRectWidth = (3 * x) + positionIcon.getWidth(this) + iconSpacing + positionLabelWidth; //(int) (200 * scaleFactor); + int leftPanelRectHeight = (int) ((lineHeight * 4) + (fontSize)); + g2d.fillRoundRect(-x, y - leftPanelRectHeight, leftPanelRectWidth, leftPanelRectHeight + x, 30, 30); + + int rpyLabelWidth = getLabelWidth(g, fontSize, rpyLabel); + int rightPanelRectWidth = (3 * x) + rpyIcon.getWidth(this) + iconSpacing + rpyLabelWidth; //(int) (200 * scaleFactor); + int rightPanelRectHeight = (int) ((lineHeight * 3) + (fontSize)); + g2d.fillRoundRect(x + widthConsole - rightPanelRectWidth, y - rightPanelRectHeight, rightPanelRectWidth + x, rightPanelRectHeight + x, 30, 30); + + Graphics2D g2dInfo = (Graphics2D) g; + RenderStringUtils.drawStringVideo(g2dInfo, font, Color.WHITE, fuelLabel, widthConsole, widthConsole, lineHeight * 2, fuelIcon, iconSpacing, lineHeight, true, true, this); + RenderStringUtils.drawStringVideo(g2dInfo, font, Color.WHITE, velLabel, widthConsole, widthConsole, y - (lineHeight * 2), velIcon, iconSpacing, lineHeight, false, true, this); + RenderStringUtils.drawStringVideo(g2dInfo, font, Color.WHITE, rpyLabel, widthConsole, widthConsole, y - (lineHeight), rpyIcon, iconSpacing, lineHeight, false, true, this); + RenderStringUtils.drawStringVideo(g2dInfo, font, Color.WHITE, depthLabel, widthConsole, x, y - (lineHeight * 3), depthIcon, iconSpacing, lineHeight, false, false, this); + RenderStringUtils.drawStringVideo(g2dInfo, font, Color.WHITE, altitudeLabel, widthConsole, x, y - (lineHeight * 2), altitudeIcon, iconSpacing, lineHeight, false, false, this); + RenderStringUtils.drawStringVideo(g2dInfo, font, Color.WHITE, positionLabel, widthConsole, x, y - (lineHeight), positionIcon, iconSpacing, lineHeight, false, false, this); + + g2dInfo.dispose(); + g2d.dispose(); + } + if (warn) { - String textWarn = "⚠"; - streamWarnJLabel.setText(textWarn); streamWarnJLabel.setSize((int) widthConsole, (int) heightConsole); streamWarnJLabel.paint(g); } } } + @Subscribe + public void mainVehicleChangeNotification(ConsoleEventMainSystemChange evt) { + fuelLabel = ""; + velLabel = ""; + rpyLabel = ""; + depthLabel = ""; + altitudeLabel = ""; + positionLabel = ""; + fuelImage = fuelEmptyImage; + } + + private int validateFontSize(Graphics g, int fontSize, String text){ + Graphics2D gTemp = (Graphics2D) g.create(); + Font font = new Font("Arial", Font.PLAIN, fontSize); + gTemp.setFont(font); + gTemp.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + gTemp.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); + FontMetrics fm = gTemp.getFontMetrics(); + int textWidth = fm.stringWidth(text); + int res = (textWidth * 100) / widthConsole; + gTemp.dispose(); + if (res > 40) { + return infoFontSize; + } + infoFontSize = fontSize; + return fontSize; + } + + private int getLabelWidth(Graphics g, int fontSize, String text){ + Graphics2D gTemp = (Graphics2D) g.create(); + Font font = new Font("Arial", Font.PLAIN, fontSize); + gTemp.setFont(font); + gTemp.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + gTemp.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); + FontMetrics fm = gTemp.getFontMetrics(); + gTemp.dispose(); + return fm.stringWidth(text); + } + private boolean isConnect() { return player != null && player.isStreamingActive() && !player.isStopRequest(); } @@ -271,7 +428,7 @@ private boolean isDisconnect() { } private void setupNoVideoImage() { - Image noVideoImage = ImageUtils.getImage(IMAGE_NO_VIDEO); + Image noVideoImage = getImage(IMAGE_NO_VIDEO); if (noVideoImage == null) { BufferedImage blackImage = ImageUtils.createCompatibleImage(1, 1, 255); blackImage.setRGB(0, 0, 0); @@ -280,7 +437,7 @@ private void setupNoVideoImage() { && noVideoImage.getHeight(null) > 0 && widthConsole >= 0 && heightConsole >= 0 //? ImageUtils.getScaledImage(noVideoImage, widthConsole, heightConsole, true) - ? Util.resizeBufferedImage(ImageUtils.toBufferedImage(ImageUtils.getImage("images/novideo.png")), new Dimension(widthConsole, heightConsole)) + ? Util.resizeBufferedImage(ImageUtils.toBufferedImage(getImage("images/novideo.png")), new Dimension(widthConsole, heightConsole)) : noVideoImage; BufferedImage onScreenImage = noVideoImage == null @@ -420,57 +577,77 @@ public void mouseClicked(MouseEvent e) { // } if (e.getButton() == MouseEvent.BUTTON3) { - popup = new JPopupMenu(); - JMenuItem item; - - popup.add(item = new JMenuItem(I18n.text("Connect to stream"), - ImageUtils.createImageIcon("images/menus/camera.png"))) - .addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - openIPCamManagementPanel(); - //service.execute(VideoReader.this::connectStream); - } - }); - item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_I, InputEvent.ALT_MASK)); - - popup.add(item = new JMenuItem(I18n.text("Close stream connection"), - ImageUtils.createImageIcon("images/menus/exit.png"))) - .addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - NeptusLog.pub().info("Closing video stream"); - service.execute(VideoReader.this::disconnectStream); + popup.show((Component) e.getSource(), e.getX(), e.getY()); + } + } + }); + } + + private void initPopupMenu() { + popup = new JPopupMenu(); + JMenuItem item; + + popup.add(item = new JMenuItem(I18n.text("Connect to stream"), + ImageUtils.createImageIcon("images/menus/camera.png"))) + .addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + openIPCamManagementPanel(); + //service.execute(VideoReader.this::connectStream); + } + }); + item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_I, InputEvent.ALT_MASK)); + + popup.add(item = new JMenuItem(I18n.text("Close stream connection"), + ImageUtils.createImageIcon("images/menus/exit.png"))) + .addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + NeptusLog.pub().info("Closing video stream"); + service.execute(VideoReader.this::disconnectStream); // noVideoLogoState = false; // isCleanTurnOffCam = true; // state = false; // ipCam = false; // closeCapture(capture); - repaint(500); - } - }); - item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_X, InputEvent.ALT_MASK)); - - popup.addSeparator(); - - popup.add(item = new JMenuItem(I18n.text("Toggle Histogram filter"), - ImageUtils.createImageIcon("images/menus/histogram.png"))) - .addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - histogramFlag = !histogramFlag; - if (player != null) { - player.setHistogramFlag(histogramFlag); - } - } - }); - item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_H, InputEvent.ALT_MASK)); - - popup.add(item = new JMenuItem(I18n.text("Maximize window"), - ImageUtils.createImageIcon("images/menus/maximize.png"))) - .addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - maximizeVideoStreamPanel(); - } - }); - item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F, InputEvent.ALT_MASK)); + repaint(500); + } + }); + item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_X, InputEvent.ALT_MASK)); + + popup.addSeparator(); + + popup.add(item = new JMenuItem(I18n.text("Toggle Histogram filter"), + ImageUtils.createImageIcon("images/menus/histogram.png"))) + .addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + histogramFlag = !histogramFlag; + if (player != null) { + player.setHistogramFlag(histogramFlag); + } + } + }); + item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_H, InputEvent.ALT_MASK)); + + popup.add(item = new JMenuItem(I18n.text("Maximize window"), + ImageUtils.createImageIcon("images/menus/maximize.png"))) + .addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + maximizeVideoStreamPanel(); + } + }); + item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F, InputEvent.ALT_MASK)); + + popup.addSeparator(); + + showInfoItem = new JCheckBoxMenuItem(I18n.text("Show vehicle information")); + + showInfoItem.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + showVehicleInformation(); + } + }); + + popup.add(showInfoItem); + // popup.addSeparator(); @@ -482,10 +659,10 @@ public void actionPerformed(ActionEvent e) { // markSnap.setEnabled(false); // popup.add(markSnap, JMenuItem.CENTER_ALIGNMENT); - popup.show((Component) e.getSource(), e.getX(), e.getY()); - } - } - }); + } + + private void showVehicleInformation() { + showVehicleInfo = !showVehicleInfo; } private void maximizeVideoStreamPanel() { @@ -506,4 +683,83 @@ private void openIPCamManagementPanel() { // JPanel for IPCam Select (MigLayout) ipCamManagementPanel.show(camUrl); } + + @Subscribe + public void on(EstimatedState msg) { + String mainVehicleId = getMainVehicleId(); + if (!msg.getSourceName().equals(mainVehicleId)) + return; + + double latDeg = Math.toDegrees(msg.getLat()); + double lonDeg = Math.toDegrees(msg.getLon()); + LocationType position = new LocationType(latDeg, lonDeg); + position.setOffsetNorth(msg.getX()); + position.setOffsetEast(msg.getY()); + position.setOffsetDown(msg.getZ()); + position.convertToAbsoluteLatLonDepth(); + positionLatDeg = position.getLatitudeDegs(); + positionLonDeg = position.getLongitudeDegs(); + String latStr = position.getLatitudeAsPrettyString(); + String lonStr = position.getLongitudeAsPrettyString(); + double roll = nomalizeAngleDegrees180(Math.toDegrees(msg.getPhi())); + String rollStr = String.format("%+04d", (int) roll).replace("+"," "); + double pitch = nomalizeAngleDegrees180(Math.toDegrees(msg.getTheta())); + String pitchStr = String.format("%+04d", (int) pitch).replace("+"," "); + double yaw = nomalizeAngleDegrees360(Math.toDegrees(msg.getPsi())); + String yawStr = String.format("%+04d", (int) yaw).replace("+"," "); + double vx = msg.getVx(); + double vy = msg.getVy(); + double vel = Math.sqrt(Math.pow(vx,2) + Math.pow(vy,2)); + String velStr = String.format("%+06.2f", vel).replace("+"," "); + if (velStr.equals("-00.00")) { + velStr = " 00.00"; + } + double depth = msg.getDepth(); + double altitude = msg.getAlt(); + + positionLabel = latStr + " / " + lonStr; + rpyLabel = rollStr + "°(R), " + pitchStr + "°(P), " + yawStr + "°(H)"; + velLabel = velStr + " m/s"; + depthLabel = String.format("%.2f m", depth); + altitudeLabel = String.format("%.2f m", altitude); + } + + @Subscribe + public void on(FuelLevel msg) { + String mainVehicleId = getMainVehicleId(); + if (!msg.getSourceName().equals(mainVehicleId)) + return; + + double fuelLevel = msg.getValue(); + + if (fuelLevel >= 90.0) { + fuelImage = fuelFullImage; + } + else if (fuelLevel >= 60.0) { + fuelImage = fuelAboveHalfImage; + } + else if (fuelLevel >= 40.0) { + fuelImage = fuelBelowHalfImage; + } + + else if (fuelLevel >= 10.0) { + fuelImage = fuelLowImage; + } + else { + fuelImage = fuelEmptyImage; + } + + fuelLabel = String.format("%02d%%", (int) fuelLevel); + } + + @Override + public void preferencesUpdated() { + if (showVehicleInfo) { + if (!Double.isNaN(positionLatDeg) && !Double.isNaN(positionLonDeg)) { + String latStr = CoordinateUtil.latitudeAsPrettyString(positionLatDeg); + String lonStr = CoordinateUtil.longitudeAsPrettyString(positionLonDeg); + positionLabel = latStr + " / " + lonStr; + } + } + } } diff --git a/plugins-dev/videoreader/src/resources/images/above_half_battery_video.png b/plugins-dev/videoreader/src/resources/images/above_half_battery_video.png new file mode 100644 index 0000000000..e698e004a8 Binary files /dev/null and b/plugins-dev/videoreader/src/resources/images/above_half_battery_video.png differ diff --git a/plugins-dev/videoreader/src/resources/images/altitude_video.png b/plugins-dev/videoreader/src/resources/images/altitude_video.png new file mode 100644 index 0000000000..c57d7368df Binary files /dev/null and b/plugins-dev/videoreader/src/resources/images/altitude_video.png differ diff --git a/plugins-dev/videoreader/src/resources/images/below_half_battery_video.png b/plugins-dev/videoreader/src/resources/images/below_half_battery_video.png new file mode 100644 index 0000000000..4c6eeb8e06 Binary files /dev/null and b/plugins-dev/videoreader/src/resources/images/below_half_battery_video.png differ diff --git a/plugins-dev/videoreader/src/resources/images/depth_video.png b/plugins-dev/videoreader/src/resources/images/depth_video.png new file mode 100644 index 0000000000..8df2ee5649 Binary files /dev/null and b/plugins-dev/videoreader/src/resources/images/depth_video.png differ diff --git a/plugins-dev/videoreader/src/resources/images/full_battery_video.png b/plugins-dev/videoreader/src/resources/images/full_battery_video.png new file mode 100644 index 0000000000..7b49f06fde Binary files /dev/null and b/plugins-dev/videoreader/src/resources/images/full_battery_video.png differ diff --git a/plugins-dev/videoreader/src/resources/images/low_battery_video.png b/plugins-dev/videoreader/src/resources/images/low_battery_video.png new file mode 100644 index 0000000000..0c3011a264 Binary files /dev/null and b/plugins-dev/videoreader/src/resources/images/low_battery_video.png differ diff --git a/plugins-dev/videoreader/src/resources/images/no_battery_video.png b/plugins-dev/videoreader/src/resources/images/no_battery_video.png new file mode 100644 index 0000000000..d380cc80a0 Binary files /dev/null and b/plugins-dev/videoreader/src/resources/images/no_battery_video.png differ diff --git a/plugins-dev/videoreader/src/resources/images/position_video.png b/plugins-dev/videoreader/src/resources/images/position_video.png new file mode 100644 index 0000000000..739dfe98d0 Binary files /dev/null and b/plugins-dev/videoreader/src/resources/images/position_video.png differ diff --git a/plugins-dev/videoreader/src/resources/images/rpy_video.png b/plugins-dev/videoreader/src/resources/images/rpy_video.png new file mode 100644 index 0000000000..f6fbfb8dd7 Binary files /dev/null and b/plugins-dev/videoreader/src/resources/images/rpy_video.png differ diff --git a/plugins-dev/videoreader/src/resources/images/speed_video.png b/plugins-dev/videoreader/src/resources/images/speed_video.png new file mode 100644 index 0000000000..0d16d0c01c Binary files /dev/null and b/plugins-dev/videoreader/src/resources/images/speed_video.png differ diff --git a/src/java/pt/lsts/neptus/util/RenderStringUtils.java b/src/java/pt/lsts/neptus/util/RenderStringUtils.java index 4efe15687a..ad5b934e6c 100644 --- a/src/java/pt/lsts/neptus/util/RenderStringUtils.java +++ b/src/java/pt/lsts/neptus/util/RenderStringUtils.java @@ -32,13 +32,20 @@ */ package pt.lsts.neptus.util; +import javax.swing.ImageIcon; +import java.awt.AlphaComposite; import java.awt.BasicStroke; import java.awt.Color; +import java.awt.Component; import java.awt.Font; +import java.awt.FontMetrics; +import java.awt.Graphics; import java.awt.Graphics2D; +import java.awt.Image; import java.awt.RenderingHints; import java.awt.Shape; import java.awt.font.GlyphVector; +import java.awt.image.ImageObserver; public class RenderStringUtils { @@ -59,31 +66,81 @@ private RenderStringUtils() { * @return */ public static void drawStringWOutline(Graphics2D g, Font font, Color textColor, Color outlineColor, String text, double x, double y) { - Graphics2D gtemp = (Graphics2D) g.create(); + Graphics2D gTemp = (Graphics2D) g.create(); try { - gtemp.setFont(font); - gtemp.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); - gtemp.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); + gTemp.setFont(font); + gTemp.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + gTemp.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); // Calculate dynamic outline thickness based on font size float outlineThickness = font.getSize() * 0.25f; - GlyphVector glyphVector = font.createGlyphVector(gtemp.getFontRenderContext(), text); + GlyphVector glyphVector = font.createGlyphVector(gTemp.getFontRenderContext(), text); Shape textShape = glyphVector.getOutline((int) (x), (int) (y)); // Draw the outline - gtemp.setColor(outlineColor); - gtemp.setStroke(new BasicStroke(outlineThickness, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND)); // Thickness of the outline - gtemp.draw(textShape); + gTemp.setColor(outlineColor); + gTemp.setStroke(new BasicStroke(outlineThickness, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND)); // Thickness of the outline + gTemp.draw(textShape); // Draw the fill - gtemp.setColor(textColor); - gtemp.fill(textShape); + gTemp.setColor(textColor); + gTemp.fill(textShape); } catch (Exception e) { e.printStackTrace(); } finally { - gtemp.dispose(); + gTemp.dispose(); + } + } + + public static void drawStringVideo(Graphics2D g, Font font, Color textColor, String text, double widthConsole, double x, double y, Image icon, int iconSpacing, int lineHeight, boolean background, boolean rightSide, ImageObserver io) { + Graphics2D gTemp = (Graphics2D) g.create(); + Graphics2D gBackground = (Graphics2D) g.create(); + try { + gTemp.setFont(font); + gTemp.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + gTemp.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); + + GlyphVector glyphVector = font.createGlyphVector(gTemp.getFontRenderContext(), text); + Shape textShape = glyphVector.getOutline((int) (x) + icon.getWidth(io) + iconSpacing, (int) (y)); + + FontMetrics fm = gTemp.getFontMetrics(); + int textWidth = fm.stringWidth(text); + int textHeight = fm.getHeight(); + + int rectX = -10; + int rectY = (int) ((int) y - (icon.getHeight(io) * 0.7) - ((double) lineHeight / 10)); + int rectWidth = (int) (x + icon.getWidth(io) + iconSpacing + textWidth) + (-2 * rectX); + int rectHeight = (int) (icon.getHeight(io) + ((double) lineHeight / 5)); + + if (rightSide) { + textShape = glyphVector.getOutline((float) ((int) (x) - textWidth - 10) , (int) (y)); + rectX = (int) x - icon.getWidth(io) - textWidth - (iconSpacing) - (-2 * rectX); + } + + if (background) { + AlphaComposite composite = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.7f); // 50% opacity + gBackground.setComposite(composite); + gBackground.setColor(Color.BLACK); + gBackground.fillRoundRect(rectX, rectY, rectWidth, rectHeight, 20, 20); + } + + gTemp.setColor(textColor); + gTemp.fill(textShape); + + if (rightSide) { + gTemp.drawImage(icon, (int) x - icon.getWidth(io) - textWidth - (iconSpacing) - 10, (int) ((int) y - (icon.getHeight(io) * 0.7)), io); + } + else { + gTemp.drawImage(icon, (int) x, (int) ((int) y - (icon.getHeight(io) * 0.7)), io); + } + } + catch (Exception e) { + e.printStackTrace(); + } finally { + gBackground.dispose(); + gTemp.dispose(); } } }