Merge pull request #395 from UniquePassive/screenshot-plugin
Screenshot plugin
This commit is contained in:
@@ -54,6 +54,8 @@ public class WidgetID
|
||||
public static final int BA_COLLECTOR_GROUP_ID = 486;
|
||||
public static final int BA_DEFENDER_GROUP_ID = 487;
|
||||
public static final int BA_HEALER_GROUP_ID = 488;
|
||||
public static final int LEVEL_UP_GROUP_ID = 233;
|
||||
public static final int QUEST_COMPLETED_GROUP_ID = 277;
|
||||
|
||||
static class WorldMap
|
||||
{
|
||||
@@ -248,4 +250,14 @@ public class WidgetID
|
||||
static final int ROLE_SPRITE = 6;
|
||||
static final int ROLE = 7;
|
||||
}
|
||||
|
||||
static class LevelUp
|
||||
{
|
||||
static final int TEXT = 1;
|
||||
}
|
||||
|
||||
static class QuestCompleted
|
||||
{
|
||||
static final int NAME_TEXT = 2;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,7 +152,13 @@ public enum WidgetInfo
|
||||
BA_DEF_CALL_TEXT(WidgetID.BA_DEFENDER_GROUP_ID, WidgetID.BarbarianAssault.TO_CALL),
|
||||
BA_DEF_LISTEN_TEXT(WidgetID.BA_DEFENDER_GROUP_ID, WidgetID.BarbarianAssault.CORRECT_STYLE),
|
||||
BA_DEF_ROLE_TEXT(WidgetID.BA_DEFENDER_GROUP_ID, WidgetID.BarbarianAssault.ROLE),
|
||||
BA_DEF_ROLE_SPRITE(WidgetID.BA_DEFENDER_GROUP_ID, WidgetID.BarbarianAssault.ROLE_SPRITE);
|
||||
BA_DEF_ROLE_SPRITE(WidgetID.BA_DEFENDER_GROUP_ID, WidgetID.BarbarianAssault.ROLE_SPRITE),
|
||||
|
||||
LEVEL_UP(WidgetID.LEVEL_UP_GROUP_ID, 0),
|
||||
LEVEL_UP_TEXT(WidgetID.LEVEL_UP_GROUP_ID, WidgetID.LevelUp.TEXT),
|
||||
|
||||
QUEST_COMPLETED(WidgetID.QUEST_COMPLETED_GROUP_ID, 0),
|
||||
QUEST_COMPLETED_NAME_TEXT(WidgetID.QUEST_COMPLETED_GROUP_ID, WidgetID.QuestCompleted.NAME_TEXT);
|
||||
|
||||
private final int groupId;
|
||||
private final int childId;
|
||||
|
||||
@@ -78,7 +78,7 @@
|
||||
<dependency>
|
||||
<groupId>org.pushingpixels</groupId>
|
||||
<artifactId>substance</artifactId>
|
||||
<version>7.1.00-rc</version>
|
||||
<version>8.0.00-dev</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.pushingpixels</groupId>
|
||||
|
||||
@@ -39,6 +39,7 @@ import joptsimple.OptionParser;
|
||||
import joptsimple.OptionSet;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.runelite.api.Client;
|
||||
import net.runelite.client.events.ClientUILoaded;
|
||||
import net.runelite.client.account.SessionManager;
|
||||
import net.runelite.client.chat.ChatMessageManager;
|
||||
import net.runelite.client.config.ConfigManager;
|
||||
@@ -55,6 +56,7 @@ public class RuneLite
|
||||
public static final File RUNELITE_DIR = new File(System.getProperty("user.home"), ".runelite");
|
||||
public static final File PROFILES_DIR = new File(RUNELITE_DIR, "profiles");
|
||||
public static final File PLUGIN_DIR = new File(RUNELITE_DIR, "plugins");
|
||||
public static final File SCREENSHOT_DIR = new File(RUNELITE_DIR, "screenshots");
|
||||
|
||||
private static Injector injector;
|
||||
private static OptionSet options;
|
||||
@@ -164,6 +166,8 @@ public class RuneLite
|
||||
pluginManager.watch();
|
||||
|
||||
SwingUtilities.invokeAndWait(() -> gui.showWithChrome(runeliteConfig.enableCustomChrome()));
|
||||
|
||||
eventBus.post(new ClientUILoaded());
|
||||
}
|
||||
|
||||
public void setGui(ClientUI gui)
|
||||
|
||||
@@ -112,6 +112,8 @@ public class Hooks
|
||||
{
|
||||
log.warn("Error during overlay rendering", ex);
|
||||
}
|
||||
|
||||
renderer.provideScreenshot(image);
|
||||
}
|
||||
|
||||
public static void drawRegion(Region region, int var1, int var2, int var3, int var4, int var5, int var6)
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright (c) 2018, Adam <Adam@sigterm.info>
|
||||
* 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.events;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class ClientUILoaded
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright (c) 2018, UniquePassive <https://github.com/uniquepassive>
|
||||
* 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.plugins.screenshot;
|
||||
|
||||
import net.runelite.client.config.Config;
|
||||
import net.runelite.client.config.ConfigGroup;
|
||||
import net.runelite.client.config.ConfigItem;
|
||||
|
||||
@ConfigGroup(
|
||||
keyName = "screenshot",
|
||||
name = "Screenshot",
|
||||
description = "Configuration for the Screenshot plugin"
|
||||
)
|
||||
public interface ScreenshotConfig extends Config
|
||||
{
|
||||
@ConfigItem(
|
||||
keyName = "includeFrame",
|
||||
name = "Include Client Frame",
|
||||
description = "Configures whether or not the client frame is included in screenshots",
|
||||
position = 0
|
||||
)
|
||||
default boolean includeFrame()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
keyName = "displayDate",
|
||||
name = "Display Date",
|
||||
description = "Configures whether or not the report button shows the date the screenshot was taken",
|
||||
position = 1
|
||||
)
|
||||
default boolean displayDate()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
keyName = "notifyWhenTaken",
|
||||
name = "Notify When Taken",
|
||||
description = "Configures whether or not you are notified when a screenshot has been taken",
|
||||
position = 2
|
||||
)
|
||||
default boolean notifyWhenTaken()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
keyName = "uploadScreenshot",
|
||||
name = "Upload To Imgur",
|
||||
description = "Configures whether or not screenshots are uploaded to Imgur and copied into your clipboard",
|
||||
position = 3
|
||||
)
|
||||
default boolean uploadScreenshot()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,365 @@
|
||||
/*
|
||||
* Copyright (c) 2018, UniquePassive <https://github.com/uniquepassive>
|
||||
* 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.plugins.screenshot;
|
||||
|
||||
import net.runelite.client.plugins.screenshot.imgur.ImageUploadRequest;
|
||||
import net.runelite.client.plugins.screenshot.imgur.ImageUploadResponse;
|
||||
import com.google.common.eventbus.Subscribe;
|
||||
import com.google.inject.Provides;
|
||||
import java.awt.Color;
|
||||
import java.awt.FontMetrics;
|
||||
import java.awt.Graphics;
|
||||
import java.awt.Point;
|
||||
import java.awt.Toolkit;
|
||||
import java.awt.TrayIcon;
|
||||
import java.awt.datatransfer.Clipboard;
|
||||
import java.awt.datatransfer.StringSelection;
|
||||
import java.io.InputStreamReader;
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JComponent;
|
||||
import javax.swing.SwingUtilities;
|
||||
import net.runelite.api.Client;
|
||||
import net.runelite.client.events.ClientUILoaded;
|
||||
import net.runelite.api.events.WidgetHiddenChanged;
|
||||
import net.runelite.api.widgets.Widget;
|
||||
import net.runelite.api.widgets.WidgetInfo;
|
||||
import net.runelite.client.Notifier;
|
||||
import net.runelite.client.RuneLite;
|
||||
import net.runelite.client.config.ConfigManager;
|
||||
import net.runelite.client.plugins.Plugin;
|
||||
import net.runelite.client.plugins.PluginDescriptor;
|
||||
import net.runelite.client.ui.ClientUI;
|
||||
import net.runelite.client.ui.FontManager;
|
||||
import net.runelite.client.ui.overlay.OverlayRenderer;
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.inject.Inject;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.runelite.http.api.RuneLiteAPI;
|
||||
import okhttp3.Call;
|
||||
import okhttp3.Callback;
|
||||
import okhttp3.MediaType;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.RequestBody;
|
||||
import okhttp3.Response;
|
||||
import org.pushingpixels.substance.internal.utils.SubstanceCoreUtilities;
|
||||
import static net.runelite.api.widgets.WidgetID.LEVEL_UP_GROUP_ID;
|
||||
import static net.runelite.api.widgets.WidgetID.QUEST_COMPLETED_GROUP_ID;
|
||||
import static net.runelite.api.widgets.WidgetInfo.TO_GROUP;
|
||||
import okhttp3.HttpUrl;
|
||||
|
||||
@PluginDescriptor(
|
||||
name = "Screenshot plugin"
|
||||
)
|
||||
@Slf4j
|
||||
public class ScreenshotPlugin extends Plugin
|
||||
{
|
||||
private static final String IMGUR_CLIENT_ID = "30d71e5f6860809";
|
||||
private static final HttpUrl IMGUR_IMAGE_UPLOAD_URL = HttpUrl.parse("https://api.imgur.com/3/image");
|
||||
private static final MediaType JSON = MediaType.parse("application/json");
|
||||
|
||||
private static final DateFormat DATE_FORMAT = new SimpleDateFormat("MMM. dd, yyyy", Locale.US);
|
||||
private static final DateFormat TIME_FORMAT = new SimpleDateFormat("h.m.s a d MMM. yyyy", Locale.US);
|
||||
|
||||
@Inject
|
||||
ScreenshotConfig config;
|
||||
|
||||
@Inject
|
||||
Notifier notifier;
|
||||
|
||||
@Inject
|
||||
Client client;
|
||||
|
||||
@Inject
|
||||
ClientUI clientUi;
|
||||
|
||||
@Inject
|
||||
OverlayRenderer overlayRenderer;
|
||||
|
||||
private JButton titleBarButton;
|
||||
|
||||
@Provides
|
||||
ScreenshotConfig getConfig(ConfigManager configManager)
|
||||
{
|
||||
return configManager.getConfig(ScreenshotConfig.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void startUp() throws Exception
|
||||
{
|
||||
// prior to UI loading this does nothing
|
||||
addButtonToTitleBar();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void shutDown() throws Exception
|
||||
{
|
||||
removeButtonFromTitlebar();
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void clientUiLoaded(ClientUILoaded e)
|
||||
{
|
||||
addButtonToTitleBar();
|
||||
}
|
||||
|
||||
private void addButtonToTitleBar()
|
||||
{
|
||||
try
|
||||
{
|
||||
BufferedImage iconImage = ImageIO.read(ScreenshotPlugin.class.getResourceAsStream("screenshot.png"));
|
||||
BufferedImage invertedIconImage = ImageIO.read(ScreenshotPlugin.class.getResourceAsStream("screenshot_inverted.png"));
|
||||
|
||||
SwingUtilities.invokeLater(() ->
|
||||
{
|
||||
titleBarButton = new JButton();
|
||||
titleBarButton.setToolTipText("Take screenshot");
|
||||
titleBarButton.addMouseListener(new MouseAdapter()
|
||||
{
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e)
|
||||
{
|
||||
super.mouseClicked(e);
|
||||
takeScreenshot(TIME_FORMAT.format(new Date()), client.getLocalPlayer() != null);
|
||||
}
|
||||
});
|
||||
|
||||
clientUi.addButtonToTitleBar(titleBarButton, iconImage, invertedIconImage, 130);
|
||||
});
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
log.warn("Error adding screenshot button to titlebar", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private void removeButtonFromTitlebar()
|
||||
{
|
||||
SwingUtilities.invokeLater(() ->
|
||||
{
|
||||
JComponent titleBar = SubstanceCoreUtilities.getTitlePaneComponent(clientUi);
|
||||
titleBar.remove(titleBarButton);
|
||||
|
||||
clientUi.revalidate();
|
||||
clientUi.repaint();
|
||||
});
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void hideWidgets(WidgetHiddenChanged event)
|
||||
{
|
||||
Widget widget = event.getWidget();
|
||||
|
||||
if (widget.isHidden())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
String fileName;
|
||||
|
||||
switch (TO_GROUP(widget.getId()))
|
||||
{
|
||||
case LEVEL_UP_GROUP_ID:
|
||||
{
|
||||
Widget textChild = client.getWidget(WidgetInfo.LEVEL_UP_TEXT);
|
||||
|
||||
if (textChild == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// "Your Firemaking level is now 9."
|
||||
String text = textChild.getText();
|
||||
|
||||
String skillName = text.substring(5, text.indexOf(" level"));
|
||||
String skillLevel = text.substring(text.lastIndexOf(" ") + 1, text.length() - 1);
|
||||
|
||||
fileName = skillName + " (" + skillLevel + ")";
|
||||
break;
|
||||
}
|
||||
case QUEST_COMPLETED_GROUP_ID:
|
||||
{
|
||||
Widget textChild = client.getWidget(WidgetInfo.QUEST_COMPLETED_NAME_TEXT);
|
||||
|
||||
if (textChild == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// "You have completed The Corsair Curse!"
|
||||
String text = textChild.getText();
|
||||
|
||||
fileName = text.substring(19, text.length() - 1);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
takeScreenshot(fileName, config.displayDate());
|
||||
}
|
||||
|
||||
private void takeScreenshot(String fileName, boolean displayDate)
|
||||
{
|
||||
overlayRenderer.requestScreenshot(image ->
|
||||
{
|
||||
BufferedImage screenshot = config.includeFrame()
|
||||
? new BufferedImage(clientUi.getWidth(), clientUi.getHeight(), BufferedImage.TYPE_INT_ARGB)
|
||||
: new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_ARGB);
|
||||
|
||||
Graphics graphics = screenshot.getGraphics();
|
||||
|
||||
int gameOffsetX = 0;
|
||||
int gameOffsetY = 0;
|
||||
|
||||
if (config.includeFrame())
|
||||
{
|
||||
// Draw the client frame onto the screenshot
|
||||
clientUi.paint(graphics);
|
||||
|
||||
// Evaluate the position of the game inside the frame
|
||||
Point gamePoint = SwingUtilities.convertPoint(client.getCanvas(), 0, 0, clientUi);
|
||||
gameOffsetX = gamePoint.x;
|
||||
gameOffsetY = gamePoint.y;
|
||||
}
|
||||
|
||||
// Draw the game onto the screenshot
|
||||
graphics.drawImage(image, gameOffsetX, gameOffsetY, null);
|
||||
|
||||
if (displayDate)
|
||||
{
|
||||
try (InputStream reportButton = ScreenshotPlugin.class.getResourceAsStream("report_button.png"))
|
||||
{
|
||||
BufferedImage reportButtonImage = ImageIO.read(reportButton);
|
||||
|
||||
int x = gameOffsetX + 403;
|
||||
int y = gameOffsetY + image.getHeight() - reportButtonImage.getHeight() - 1;
|
||||
|
||||
graphics.drawImage(reportButtonImage, x, y, null);
|
||||
|
||||
graphics.setFont(FontManager.getRunescapeSmallFont());
|
||||
FontMetrics fontMetrics = graphics.getFontMetrics();
|
||||
|
||||
String date = DATE_FORMAT.format(new Date());
|
||||
int dateWidth = fontMetrics.stringWidth(date);
|
||||
int dateHeight = fontMetrics.getHeight();
|
||||
|
||||
int textX = x + reportButtonImage.getWidth() / 2 - dateWidth / 2;
|
||||
int textY = y + reportButtonImage.getHeight() / 2 + dateHeight / 2;
|
||||
|
||||
graphics.setColor(Color.BLACK);
|
||||
graphics.drawString(date, textX + 1, textY + 1);
|
||||
|
||||
graphics.setColor(Color.WHITE);
|
||||
graphics.drawString(date, textX, textY);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
log.warn("error displaying date on screenshot", ex);
|
||||
}
|
||||
}
|
||||
|
||||
File playerFolder = RuneLite.SCREENSHOT_DIR;
|
||||
|
||||
if (client.getLocalPlayer() != null)
|
||||
{
|
||||
playerFolder = new File(RuneLite.SCREENSHOT_DIR, client.getLocalPlayer().getName());
|
||||
}
|
||||
|
||||
playerFolder.mkdirs();
|
||||
|
||||
try
|
||||
{
|
||||
File screenshotFile = new File(playerFolder, fileName + ".png");
|
||||
|
||||
ImageIO.write(screenshot, "PNG", screenshotFile);
|
||||
|
||||
if (config.uploadScreenshot())
|
||||
{
|
||||
uploadScreenshot(screenshotFile);
|
||||
}
|
||||
else if (config.notifyWhenTaken())
|
||||
{
|
||||
notifier.notify("A screenshot was saved to " + screenshotFile, TrayIcon.MessageType.INFO);
|
||||
}
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
log.warn("error writing screenshot", ex);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void uploadScreenshot(File screenshotFile) throws IOException
|
||||
{
|
||||
String json = RuneLiteAPI.GSON.toJson(new ImageUploadRequest(screenshotFile));
|
||||
|
||||
Request request = new Request.Builder()
|
||||
.url(IMGUR_IMAGE_UPLOAD_URL)
|
||||
.addHeader("Authorization", "Client-ID " + IMGUR_CLIENT_ID)
|
||||
.post(RequestBody.create(JSON, json))
|
||||
.build();
|
||||
|
||||
RuneLiteAPI.CLIENT.newCall(request).enqueue(new Callback()
|
||||
{
|
||||
@Override
|
||||
public void onFailure(Call call, IOException ex)
|
||||
{
|
||||
log.warn("error uploading screenshot", ex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResponse(Call call, Response response) throws IOException
|
||||
{
|
||||
InputStream in = response.body().byteStream();
|
||||
ImageUploadResponse imageUploadResponse = RuneLiteAPI.GSON
|
||||
.fromJson(new InputStreamReader(in), ImageUploadResponse.class);
|
||||
|
||||
if (imageUploadResponse.isSuccess())
|
||||
{
|
||||
String link = imageUploadResponse.getData().getLink();
|
||||
|
||||
StringSelection selection = new StringSelection(link);
|
||||
Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
|
||||
clipboard.setContents(selection, selection);
|
||||
|
||||
if (config.notifyWhenTaken())
|
||||
{
|
||||
notifier.notify("A screenshot was uploaded and inserted into your clipboard!", TrayIcon.MessageType.INFO);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright (c) 2018, UniquePassive <https://github.com/uniquepassive>
|
||||
* 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.plugins.screenshot.imgur;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.util.Base64;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class ImageUploadRequest
|
||||
{
|
||||
private final String image;
|
||||
private final String type;
|
||||
|
||||
public ImageUploadRequest(File imageFile) throws IOException
|
||||
{
|
||||
this.image = Base64.getEncoder().encodeToString(Files.readAllBytes(imageFile.toPath()));
|
||||
this.type = "base64";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright (c) 2018, UniquePassive <https://github.com/uniquepassive>
|
||||
* 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.plugins.screenshot.imgur;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class ImageUploadResponse
|
||||
{
|
||||
private Data data;
|
||||
private boolean success;
|
||||
|
||||
@lombok.Data
|
||||
public static class Data
|
||||
{
|
||||
private String link;
|
||||
}
|
||||
}
|
||||
@@ -30,19 +30,28 @@ import java.awt.AWTException;
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.Canvas;
|
||||
import java.awt.Cursor;
|
||||
import java.awt.Desktop;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.Frame;
|
||||
import java.awt.Image;
|
||||
import java.awt.SystemTray;
|
||||
import java.awt.TrayIcon;
|
||||
import java.awt.event.ComponentAdapter;
|
||||
import java.awt.event.ComponentEvent;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.awt.event.WindowAdapter;
|
||||
import java.awt.event.WindowEvent;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.IOException;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.util.Enumeration;
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.swing.BoxLayout;
|
||||
import javax.swing.ImageIcon;
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JComponent;
|
||||
import javax.swing.JFrame;
|
||||
import javax.swing.JOptionPane;
|
||||
import javax.swing.JPanel;
|
||||
@@ -59,13 +68,16 @@ import net.runelite.api.Client;
|
||||
import net.runelite.api.GameState;
|
||||
import net.runelite.client.RuneLiteProperties;
|
||||
import org.pushingpixels.substance.api.skin.SubstanceGraphiteLookAndFeel;
|
||||
import org.pushingpixels.substance.internal.ui.SubstanceRootPaneUI;
|
||||
import org.pushingpixels.substance.internal.SubstanceSynapse;
|
||||
import org.pushingpixels.substance.internal.utils.SubstanceCoreUtilities;
|
||||
import org.pushingpixels.substance.internal.utils.SubstanceTitlePaneUtilities;
|
||||
|
||||
@Slf4j
|
||||
public class ClientUI extends JFrame
|
||||
{
|
||||
private static final int PANEL_EXPANDED_WIDTH = PluginPanel.PANEL_WIDTH + PluginPanel.SCROLLBAR_WIDTH;
|
||||
private static final BufferedImage ICON;
|
||||
private static final String DISCORD_INVITE = "https://discord.gg/R4BQ8tU";
|
||||
|
||||
@Getter
|
||||
private TrayIcon trayIcon;
|
||||
@@ -150,7 +162,36 @@ public class ClientUI extends JFrame
|
||||
setLocationRelativeTo(getOwner());
|
||||
if (customChrome)
|
||||
{
|
||||
new TitleBarPane(this.getRootPane(), (SubstanceRootPaneUI) this.getRootPane().getUI()).editTitleBar(this);
|
||||
try
|
||||
{
|
||||
BufferedImage discordIcon = ImageIO.read(ClientUI.class.getResourceAsStream("discord.png"));
|
||||
BufferedImage invertedIcon = ImageIO.read(ClientUI.class.getResourceAsStream("discord_inverted.png"));
|
||||
|
||||
JButton discordButton = new JButton();
|
||||
discordButton.setToolTipText("Join Discord");
|
||||
discordButton.addMouseListener(new MouseAdapter()
|
||||
{
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e)
|
||||
{
|
||||
super.mouseClicked(e);
|
||||
try
|
||||
{
|
||||
Desktop.getDesktop().browse(new URL(DISCORD_INVITE).toURI());
|
||||
}
|
||||
catch (IOException | URISyntaxException ex)
|
||||
{
|
||||
log.warn("error opening browser", ex);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
addButtonToTitleBar(discordButton, discordIcon, invertedIcon, 100);
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
log.warn("unable to load discord button", ex);
|
||||
}
|
||||
}
|
||||
|
||||
setVisible(true);
|
||||
@@ -172,6 +213,44 @@ public class ClientUI extends JFrame
|
||||
}
|
||||
}
|
||||
|
||||
public void addButtonToTitleBar(JButton button, Image iconImage, Image invertedIconImage, int xOffset)
|
||||
{
|
||||
JComponent titleBar = SubstanceCoreUtilities.getTitlePaneComponent(this);
|
||||
|
||||
if (titleBar == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int size = titleBar.getHeight() - 6;
|
||||
|
||||
ImageIcon icon = new ImageIcon(iconImage.getScaledInstance(size, size, Image.SCALE_SMOOTH));
|
||||
ImageIcon invertedIcon = new ImageIcon(invertedIconImage.getScaledInstance(size, size, Image.SCALE_SMOOTH));
|
||||
|
||||
button.setIcon(icon);
|
||||
button.setRolloverIcon(invertedIcon);
|
||||
button.putClientProperty(SubstanceSynapse.FLAT_LOOK, Boolean.TRUE);
|
||||
button.putClientProperty(SubstanceTitlePaneUtilities.EXTRA_COMPONENT_KIND, SubstanceTitlePaneUtilities.ExtraComponentKind.TRAILING);
|
||||
button.setFocusable(false);
|
||||
button.setBounds(titleBar.getWidth() - xOffset, 2,
|
||||
icon.getIconWidth() + 4, icon.getIconHeight() + 2);
|
||||
|
||||
titleBar.addComponentListener(new ComponentAdapter()
|
||||
{
|
||||
@Override
|
||||
public void componentResized(ComponentEvent e)
|
||||
{
|
||||
super.componentResized(e);
|
||||
button.setBounds(titleBar.getWidth() - xOffset, 1, button.getWidth(), button.getHeight());
|
||||
}
|
||||
});
|
||||
|
||||
titleBar.add(button);
|
||||
|
||||
revalidate();
|
||||
repaint();
|
||||
}
|
||||
|
||||
private static void setUIFont(FontUIResource f)
|
||||
{
|
||||
final Enumeration keys = UIManager.getDefaults().keys();
|
||||
|
||||
@@ -1,126 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2017. l2-
|
||||
*
|
||||
* 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;
|
||||
|
||||
import java.awt.Desktop;
|
||||
import java.awt.Image;
|
||||
import java.awt.event.ComponentAdapter;
|
||||
import java.awt.event.ComponentEvent;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.IOException;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.swing.ImageIcon;
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JComponent;
|
||||
import javax.swing.JFrame;
|
||||
import javax.swing.JRootPane;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.pushingpixels.substance.api.SubstanceLookAndFeel;
|
||||
import org.pushingpixels.substance.internal.ui.SubstanceRootPaneUI;
|
||||
import org.pushingpixels.substance.internal.utils.SubstanceTitlePane;
|
||||
|
||||
@Slf4j
|
||||
public class TitleBarPane extends SubstanceTitlePane
|
||||
{
|
||||
private static final String DISCORD_INVITE = "https://discord.gg/R4BQ8tU";
|
||||
|
||||
private BufferedImage discordIcon;
|
||||
private BufferedImage invertedIcon;
|
||||
|
||||
public TitleBarPane(JRootPane root, SubstanceRootPaneUI ui)
|
||||
{
|
||||
super(root, ui);
|
||||
|
||||
try
|
||||
{
|
||||
discordIcon = ImageIO.read(ClientUI.class.getResourceAsStream("discord.png"));
|
||||
invertedIcon = ImageIO.read(ClientUI.class.getResourceAsStream("discord_inverted.png"));
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
log.warn("unable to load discord icons", ex);
|
||||
}
|
||||
}
|
||||
|
||||
public void editTitleBar(JFrame frame)
|
||||
{
|
||||
if (discordIcon == null || invertedIcon == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
JComponent titleBar = SubstanceLookAndFeel.getTitlePaneComponent(frame);
|
||||
|
||||
JButton discordButton = new JButton();
|
||||
int heigth = titleBar.getHeight() - 3;
|
||||
int width = heigth;
|
||||
int x = titleBar.getWidth() - 100;
|
||||
int y = 1;
|
||||
ImageIcon icon = new ImageIcon(discordIcon.getScaledInstance(width - 3, heigth - 3, Image.SCALE_SMOOTH));
|
||||
ImageIcon invIcon = new ImageIcon(invertedIcon.getScaledInstance(width - 3, heigth - 3, Image.SCALE_SMOOTH));
|
||||
|
||||
discordButton.setIcon(icon);
|
||||
discordButton.setRolloverIcon(invIcon);
|
||||
discordButton.putClientProperty("substancelaf.componentFlat", Boolean.TRUE);
|
||||
discordButton.putClientProperty("substancelaf.internal.titlePane.extraComponentKind", ExtraComponentKind.TRAILING);
|
||||
discordButton.setFocusable(false);
|
||||
discordButton.setBounds(x, y, icon.getIconWidth() + 4, icon.getIconHeight() + 2);
|
||||
discordButton.setToolTipText("Join Discord");
|
||||
|
||||
discordButton.addMouseListener(new MouseAdapter()
|
||||
{
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e)
|
||||
{
|
||||
super.mouseClicked(e);
|
||||
try
|
||||
{
|
||||
Desktop.getDesktop().browse(new URL(DISCORD_INVITE).toURI());
|
||||
}
|
||||
catch (IOException | URISyntaxException ex)
|
||||
{
|
||||
log.warn("error opening browser", ex);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
titleBar.addComponentListener(new ComponentAdapter()
|
||||
{
|
||||
@Override
|
||||
public void componentResized(ComponentEvent e)
|
||||
{
|
||||
super.componentResized(e);
|
||||
discordButton.setBounds(titleBar.getWidth() - 100, y, discordButton.getWidth(), discordButton.getHeight());
|
||||
}
|
||||
});
|
||||
|
||||
titleBar.add(discordButton);
|
||||
}
|
||||
}
|
||||
@@ -34,11 +34,14 @@ import java.awt.Rectangle;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Provider;
|
||||
import javax.inject.Singleton;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.runelite.api.Client;
|
||||
import net.runelite.api.GameState;
|
||||
import net.runelite.api.widgets.Widget;
|
||||
@@ -51,6 +54,7 @@ import net.runelite.client.ui.overlay.infobox.InfoBoxOverlay;
|
||||
import net.runelite.client.ui.overlay.tooltip.TooltipOverlay;
|
||||
|
||||
@Singleton
|
||||
@Slf4j
|
||||
public class OverlayRenderer
|
||||
{
|
||||
private static final int BORDER_TOP = 25;
|
||||
@@ -75,6 +79,8 @@ public class OverlayRenderer
|
||||
private BufferedImage surface;
|
||||
private Graphics2D surfaceGraphics;
|
||||
|
||||
private ConcurrentLinkedQueue<Consumer<BufferedImage>> screenshotRequests = new ConcurrentLinkedQueue<>();
|
||||
|
||||
@Subscribe
|
||||
public void onResizableChanged(ResizeableChanged event)
|
||||
{
|
||||
@@ -299,4 +305,25 @@ public class OverlayRenderer
|
||||
subGraphics.dispose();
|
||||
return dimension;
|
||||
}
|
||||
|
||||
public void provideScreenshot(BufferedImage image)
|
||||
{
|
||||
Consumer<BufferedImage> consumer;
|
||||
while ((consumer = screenshotRequests.poll()) != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
consumer.accept(image);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
log.warn("error in screenshot callback", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void requestScreenshot(Consumer<BufferedImage> consumer)
|
||||
{
|
||||
screenshotRequests.add(consumer);
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 1.3 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 692 B |
Binary file not shown.
|
After Width: | Height: | Size: 677 B |
Reference in New Issue
Block a user