From da096a6407c76f9f387c1b63337f351d7d54d645 Mon Sep 17 00:00:00 2001 From: Max Weber Date: Sun, 4 Mar 2018 12:43:26 -0700 Subject: [PATCH] specorb: Cache draws to reduce allocations --- .../plugins/specorb/SpecOrbOverlay.java | 35 ++- .../client/plugins/specorb/SpecOrbPlugin.java | 22 -- .../client/ui/overlay/OverlayUtil.java | 145 ---------- .../ui/overlay/components/MinimapOrb.java | 261 ++++++++++++++++++ .../components}/minimap_orb_background.png | Bin 5 files changed, 284 insertions(+), 179 deletions(-) create mode 100644 runelite-client/src/main/java/net/runelite/client/ui/overlay/components/MinimapOrb.java rename runelite-client/src/main/resources/net/runelite/client/{plugins/specorb => ui/overlay/components}/minimap_orb_background.png (100%) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/specorb/SpecOrbOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/specorb/SpecOrbOverlay.java index 9781541151..03816b52a9 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/specorb/SpecOrbOverlay.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/specorb/SpecOrbOverlay.java @@ -24,9 +24,13 @@ */ package net.runelite.client.plugins.specorb; +import com.google.inject.Singleton; import java.awt.Color; import java.awt.Dimension; import java.awt.Graphics2D; +import java.awt.image.BufferedImage; +import java.io.IOException; +import javax.imageio.ImageIO; import javax.inject.Inject; import net.runelite.api.Client; import net.runelite.api.Point; @@ -38,8 +42,9 @@ import net.runelite.api.widgets.WidgetInfo; import net.runelite.client.ui.overlay.Overlay; import net.runelite.client.ui.overlay.OverlayLayer; import net.runelite.client.ui.overlay.OverlayPosition; -import net.runelite.client.ui.overlay.OverlayUtil; +import net.runelite.client.ui.overlay.components.MinimapOrb; +@Singleton public class SpecOrbOverlay extends Overlay { private static final int RECHARGE_TIME_TICKS = 51; @@ -57,20 +62,27 @@ public class SpecOrbOverlay extends Overlay private static final int RUN_ORB_Y_RESIZABLE = 103; private static final Color SPECIAL_ORB_BACKGROUND_COLOR = new Color(51, 102, 255); - private static final Color SPECIAL_ORB_RECHARGE_COLOR = new Color(153, 204, 255, 50); private final Client client; - private final SpecOrbPlugin plugin; + private final MinimapOrb orb; private int lastSpecialPercent = 0; private int tickCounter = 0; @Inject - public SpecOrbOverlay(Client client, SpecOrbPlugin plugin) + public SpecOrbOverlay(Client client) { setPosition(OverlayPosition.DYNAMIC); setLayer(OverlayLayer.ABOVE_WIDGETS); this.client = client; - this.plugin = plugin; + try + { + BufferedImage icon = ImageIO.read(getClass().getResourceAsStream("special_orb_icon.png")); + orb = new MinimapOrb(SPECIAL_ORB_BACKGROUND_COLOR, SPECIAL_ORB_BACKGROUND_COLOR, icon); + } + catch (IOException e) + { + throw new RuntimeException(e); + } } @Override @@ -125,20 +137,19 @@ public class SpecOrbOverlay extends Overlay graphics.setColor(SPECIAL_ORB_BACKGROUND_COLOR); - boolean specialAttackEnabled = client.getSetting(Setting.SPECIAL_ATTACK_ENABLED) == 1; + orb.setEnabled(client.getSetting(Setting.SPECIAL_ATTACK_ENABLED) == 1); // draw relative to run orb Point runOrbPoint = runOrb.getCanvasLocation(); - Point specOrbPoint = new Point(runOrbPoint.getX() + (client.isResized() ? SPEC_ORB_X_RESIZABLE : SPEC_ORB_X_FIXED), + java.awt.Point specOrbPoint = new java.awt.Point(runOrbPoint.getX() + (client.isResized() ? SPEC_ORB_X_RESIZABLE : SPEC_ORB_X_FIXED), runOrbPoint.getY() + (client.isResized() ? SPEC_ORB_Y_RESIZABLE : SPEC_ORB_Y_FIXED)); double specialPercent = client.getSetting(Setting.SPECIAL_ATTACK_PERCENT) / 1000.0; - double specialRechargePercent = tickCounter / (double) RECHARGE_TIME_TICKS; + orb.setPercentFilled(specialPercent); + orb.setAmount((int) (specialPercent * 100)); + orb.setRechargePercentFilled(tickCounter / (double) RECHARGE_TIME_TICKS); - OverlayUtil.drawMinimapOrb(graphics, specOrbPoint, specialPercent, - SPECIAL_ORB_RECHARGE_COLOR, specialRechargePercent, - plugin.getMinimapOrbBackground(), plugin.getSpecialAttackIcon(), - (int) (specialPercent * 100), specialAttackEnabled); + orb.render(graphics, specOrbPoint); return null; } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/specorb/SpecOrbPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/specorb/SpecOrbPlugin.java index 1d57b4c4d5..693e6c5de1 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/specorb/SpecOrbPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/specorb/SpecOrbPlugin.java @@ -25,8 +25,6 @@ package net.runelite.client.plugins.specorb; import com.google.common.eventbus.Subscribe; -import java.awt.Image; -import javax.imageio.ImageIO; import javax.inject.Inject; import net.runelite.api.events.GameTick; import net.runelite.api.events.VarbitChanged; @@ -39,9 +37,6 @@ import net.runelite.client.ui.overlay.Overlay; ) public class SpecOrbPlugin extends Plugin { - private Image minimapOrbBackground; - private Image specialAttackIcon; - @Inject private SpecOrbOverlay overlay; @@ -51,13 +46,6 @@ public class SpecOrbPlugin extends Plugin return overlay; } - @Override - protected void startUp() throws Exception - { - minimapOrbBackground = ImageIO.read(getClass().getResourceAsStream("minimap_orb_background.png")); - specialAttackIcon = ImageIO.read(getClass().getResourceAsStream("special_orb_icon.png")); - } - @Subscribe public void onVarbitChanged(VarbitChanged event) { @@ -69,14 +57,4 @@ public class SpecOrbPlugin extends Plugin { overlay.onTick(event); } - - public Image getMinimapOrbBackground() - { - return minimapOrbBackground; - } - - public Image getSpecialAttackIcon() - { - return specialAttackIcon; - } } diff --git a/runelite-client/src/main/java/net/runelite/client/ui/overlay/OverlayUtil.java b/runelite-client/src/main/java/net/runelite/client/ui/overlay/OverlayUtil.java index 10ab56e91e..a1cbd38c56 100644 --- a/runelite-client/src/main/java/net/runelite/client/ui/overlay/OverlayUtil.java +++ b/runelite-client/src/main/java/net/runelite/client/ui/overlay/OverlayUtil.java @@ -27,15 +27,9 @@ package net.runelite.client.ui.overlay; import java.awt.BasicStroke; import java.awt.Color; import java.awt.Dimension; -import java.awt.Font; -import java.awt.FontMetrics; import java.awt.Graphics2D; -import java.awt.Image; -import java.awt.Paint; import java.awt.Polygon; -import java.awt.RadialGradientPaint; import java.awt.RenderingHints; -import java.awt.geom.Point2D; import java.awt.image.BufferedImage; import net.runelite.api.Actor; import net.runelite.api.Client; @@ -51,7 +45,6 @@ import net.runelite.client.ui.FontManager; */ public class OverlayUtil { - public static void renderPolygon(Graphics2D graphics, Polygon poly, Color color) { graphics.setColor(color); @@ -61,144 +54,6 @@ public class OverlayUtil graphics.fillPolygon(poly); } - /** - * Draws a minimap orb like the prayer/HP/run orbs. The background color can be set by graphics.setColor() before calling this - * - * @param graphics graphics to draw to - * @param pos top left position of the orb - * @param percentFilled how far filled the orb should be - * @param rechargeColor color of the recharge meter of this orb. This can be used to track things like hp regen or special regen. - * @param rechargePercentFilled how far through the recharge this stat is - * @param minimapOrbBackground background image of the minimap orb - * @param overlayImage - * @param amount number to display on the orb - * @param enabled - */ - public static void drawMinimapOrb(Graphics2D graphics, Point pos, double percentFilled, Color rechargeColor, double rechargePercentFilled, Image minimapOrbBackground, Image overlayImage, int amount, boolean enabled) - { - Color startColor = graphics.getColor(); - - Point orbPos = new Point(pos.getX() + 26, pos.getY() + 2); - - graphics.setColor(new Color(20, 20, 20)); - // draw black background for the orb when it's partially missing - drawOrbPercent(graphics, orbPos, 1, 28, false, false); - graphics.setColor(startColor); - - // draw orb with shading - drawOrbPercent(graphics, orbPos, percentFilled, 28, true, enabled); - - graphics.setColor(rechargeColor); - // draw recharge - drawOrbPercent(graphics, orbPos, rechargePercentFilled, 28, false, false); - graphics.setColor(startColor); - - // draw background - graphics.drawImage(minimapOrbBackground, pos.getX(), pos.getY(), null); - // draw overlay - graphics.drawImage(overlayImage, pos.getX() + 33, pos.getY() + 10, null); - - drawOrbAmount(graphics, pos, amount);//draw number on orb - - } - - /** - * Draws the amount text on minimap orbs. For example HP number on the HP minimap orb - * - * @param graphics graphics to draw on - * @param pos start position - * @param amount number amount - */ - private static void drawOrbAmount(Graphics2D graphics, Point pos, int amount) - { - Color startColor = graphics.getColor(); - - Font font = FontManager.getRunescapeSmallFont(); - graphics.setFont(font); - - FontMetrics fm = graphics.getFontMetrics(); - - String numberString = Integer.toString(amount); - Point numberPos = new Point(pos.getX() + 24 - fm.stringWidth(numberString), pos.getY() + 27); - graphics.setColor(Color.black); - graphics.drawString(numberString, numberPos.getX() + 1, numberPos.getY() + 1);//black shadow on text - graphics.setColor(Color.green); - graphics.drawString(numberString, numberPos.getX(), numberPos.getY()); - - graphics.setColor(startColor); - } - - /** - * Renders a orb similar to the health/prayer orbs already in the client. - * This has a slight shadow effect similar to the clients orbs if shadow = true - * - * @param graphics graphics to draw to - * @param pos Top left position of the orb - * @param percent 0.0-1.0 percent of how filled the orb is - * @param diameter diameter of the orb - * @param shadow draw shadow - */ - private static void drawOrbPercent(Graphics2D graphics, Point pos, double percent, int diameter, boolean shadow, boolean enabled) - { - BufferedImage bufferedImage = new BufferedImage(diameter, diameter, BufferedImage.TYPE_INT_ARGB); - Graphics2D bufferedImageGraphics = bufferedImage.createGraphics(); - float[] dist = {0.2f, 1f}; - - Color setColor = graphics.getColor(); - - int r = setColor.getRed(); - int g = setColor.getGreen(); - int b = setColor.getBlue(); - int a = setColor.getAlpha(); - - Paint startPaint = bufferedImageGraphics.getPaint(); - - if (shadow) - { - float darkenMultiplier = 0.4f; - Point2D center = new Point2D.Float(diameter / 2.7f, diameter / 2.7f); - - if (enabled) - { - // Darken and move center to make it look like the orb is pressed down - darkenMultiplier = 0.20f; - center = new Point2D.Float((diameter / 1.8f), (diameter / 1.8f)); - dist = new float[]{0.7f, 1f}; - } - - // Darken the base color to create a shadow effect on the edges of the orb - r *= darkenMultiplier; - g *= darkenMultiplier; - b *= darkenMultiplier; - - Color[] colors = {setColor, new Color(r, g, b, a)}; - - // Divided by 2.7f for a gradient in the top left of the orb - RadialGradientPaint p = new RadialGradientPaint(center, - diameter / 2.0f, dist, colors); - - bufferedImageGraphics.setPaint(p); - - } - else - { - bufferedImageGraphics.setColor(setColor); - } - - bufferedImageGraphics.fillArc(0, 0, diameter, diameter, 0, 360); - if (percent < 1) - { - // Clear the top part of the orb. if we input 90% we need to clear the top 10% - bufferedImageGraphics.setBackground(new Color(255, 255, 255, 0)); - bufferedImageGraphics.clearRect(0, 0, diameter, (int) ((diameter) * (1 - percent))); - } - - graphics.drawImage(bufferedImage, pos.getX(), pos.getY(), null); - - bufferedImageGraphics.setPaint(startPaint); - bufferedImageGraphics.dispose(); - } - public static void renderMinimapLocation(Graphics2D graphics, Point mini, Color color) { graphics.setColor(color); diff --git a/runelite-client/src/main/java/net/runelite/client/ui/overlay/components/MinimapOrb.java b/runelite-client/src/main/java/net/runelite/client/ui/overlay/components/MinimapOrb.java new file mode 100644 index 0000000000..3d03186940 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/ui/overlay/components/MinimapOrb.java @@ -0,0 +1,261 @@ +/* + * Copyright (c) 2018 Abex + * Copyright (c) 2016-2017, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.ui.overlay.components; + +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Font; +import java.awt.FontMetrics; +import java.awt.Graphics2D; +import java.awt.Point; +import java.awt.RadialGradientPaint; +import java.awt.Shape; +import java.awt.geom.Point2D; +import java.awt.image.BufferedImage; +import java.io.IOException; +import javax.imageio.ImageIO; +import lombok.Getter; +import lombok.Setter; +import net.runelite.client.ui.FontManager; +import net.runelite.client.ui.overlay.RenderableEntity; + +public class MinimapOrb implements RenderableEntity +{ + private static final int ORB_X = 26; + private static final int ORB_Y = 2; + private static final int ORB_W = 29; + + private static final Color BACKGROUND_COLOR = new Color(20, 20, 20); + private static final Color HIGHLIGHT_COLOR = new Color(255, 255, 255, 50); + + // Frame + private static final BufferedImage FRAME; + + static + { + try + { + FRAME = ImageIO.read(MinimapOrb.class.getResourceAsStream("minimap_orb_background.png")); + } + catch (IOException e) + { + throw new RuntimeException(e); + } + } + + // black background + private static final BufferedImage BACKGROUND = createOval(BACKGROUND_COLOR); + + // Highlight for recharge + private static final BufferedImage HIGHLIGHT = createOval(HIGHLIGHT_COLOR); + + private static BufferedImage createOval(Color color) + { + BufferedImage img = new BufferedImage(ORB_W, ORB_W, BufferedImage.TYPE_INT_ARGB); + Graphics2D g = img.createGraphics(); + + g.setColor(color); + g.fillOval(0, 0, ORB_W, ORB_W); + + // fillOval seems to be slightly different than the circle in the orb + // so we just draw part of the frame ontop so it doesn't overdraw when we don't draw the + // frame in the normal render (for hitpoints, etc.) + g.drawImage(FRAME, -ORB_X, -ORB_Y, null); + + return img; + } + + // Color and reflection highlight + private final BufferedImage fill; + // Shadow for when enabled + private final BufferedImage fillEnabled; + + /** + * icon to draw ontop + */ + @Getter + @Setter + private BufferedImage overlay; + + /** + * how far filled the orb should be + */ + @Getter + @Setter + private double percentFilled; + + /** + * how far through the recharge this stat is + */ + @Getter + @Setter + private double rechargePercentFilled; + + /** + * number to display on the orb + */ + @Getter + @Setter + private int amount; + + /** + * should it look like it pressed down + */ + @Getter + @Setter + private boolean enabled; + + /** + * Creates a minimap orb like the prayer/HP/run orbs including a background and frame + */ + public MinimapOrb(Color color, Color enabledColor, BufferedImage overlay) + { + this.fill = createFill(color, false); + this.fillEnabled = createFill(enabledColor, true); + + this.overlay = overlay; + } + + + /** + * Creates a minimap orb like the prayer/HP/run orbs without the background, frame, etc. + * The only part that is rendered is the icon and the recharge + */ + public MinimapOrb(BufferedImage overlay) + { + this.fill = null; + this.fillEnabled = null; + this.overlay = overlay; + } + + private static BufferedImage createFill(Color color, boolean enabled) + { + BufferedImage bufferedImage = new BufferedImage(ORB_W, ORB_W, BufferedImage.TYPE_INT_ARGB); + Graphics2D graphics = bufferedImage.createGraphics(); + float[] dist = {0.2f, 1f}; + + int r = color.getRed(); + int g = color.getGreen(); + int b = color.getBlue(); + int a = color.getAlpha(); + + float darkenMultiplier = 0.4f; + Point2D center = new Point2D.Float(ORB_W / 2.7f, ORB_W / 2.7f); + + if (enabled) + { + // Darken and move center to make it look like the orb is pressed down + darkenMultiplier = 0.20f; + center = new Point2D.Float((ORB_W / 1.8f), (ORB_W / 1.8f)); + dist = new float[]{0.7f, 1f}; + } + + // Darken the base color to create a shadow effect on the edges of the orb + r *= darkenMultiplier; + g *= darkenMultiplier; + b *= darkenMultiplier; + + Color[] colors = {color, new Color(r, g, b, a)}; + + // Divided by 2.7f for a gradient in the top left of the orb + graphics.setPaint(new RadialGradientPaint(center, ORB_W / 2.0f, dist, colors)); + + graphics.fillOval(0, 0, ORB_W, ORB_W); + + return bufferedImage; + } + + /** + * Draws a minimap orb like the prayer/HP/run orbs. + * + * @param graphics graphics to draw to + * @param pos top left position of the orb + */ + @Override + public Dimension render(Graphics2D graphics, Point pos) + { + int orbX = pos.x + ORB_X; + int orbY = pos.y + ORB_Y; + + percentFilled = Math.max(Math.min(percentFilled, 1), 0); + rechargePercentFilled = Math.max(Math.min(rechargePercentFilled, 1), 0); + + if (fill != null) + { + graphics.drawImage(BACKGROUND, orbX, orbY, null); + } + + Shape clip = graphics.getClip(); + + if (fill != null) + { + int height = (int) (percentFilled * ORB_W); + graphics.setClip(orbX, orbY + (ORB_W - height), ORB_W, height); + graphics.drawImage(enabled ? fillEnabled : fill, orbX, orbY, null); + } + + { + int height = (int) (rechargePercentFilled * ORB_W); + graphics.setClip(orbX, orbY + (ORB_W - height), ORB_W, height); + graphics.drawImage(HIGHLIGHT, orbX, orbY, null); + } + graphics.setClip(clip); + + if (fill != null) + { + graphics.drawImage(FRAME, pos.x, pos.y, null); + } + + graphics.drawImage( + overlay, + orbX + (1 + ORB_W - overlay.getWidth()) / 2, + orbY + (1 + ORB_W - overlay.getHeight()) / 2, + null + ); + + if (fill != null) + { + Font font = FontManager.getRunescapeSmallFont(); + graphics.setFont(font); + + FontMetrics fm = graphics.getFontMetrics(); + + String numberString = Integer.toString(amount); + + int textX = pos.x + (ORB_X - fm.stringWidth(numberString)) / 2; + int textY = pos.y + 27; + + graphics.setColor(Color.black); + graphics.drawString(numberString, textX + 1, textY + 1); + + Color textColor = Color.getHSBColor((float) percentFilled * (120.f / 360.f), 1, 1); + graphics.setColor(textColor); + graphics.drawString(numberString, textX, textY); + } + + return new Dimension(BACKGROUND.getWidth(), BACKGROUND.getHeight()); + } +} diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/specorb/minimap_orb_background.png b/runelite-client/src/main/resources/net/runelite/client/ui/overlay/components/minimap_orb_background.png similarity index 100% rename from runelite-client/src/main/resources/net/runelite/client/plugins/specorb/minimap_orb_background.png rename to runelite-client/src/main/resources/net/runelite/client/ui/overlay/components/minimap_orb_background.png