forked from j62/ctbrec
Add model portrait column to recorded models tab
This commit is contained in:
parent
016b5dc7f1
commit
a258498117
|
@ -81,6 +81,10 @@
|
||||||
<groupId>org.openjfx</groupId>
|
<groupId>org.openjfx</groupId>
|
||||||
<artifactId>javafx-media</artifactId>
|
<artifactId>javafx-media</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.openjfx</groupId>
|
||||||
|
<artifactId>javafx-swing</artifactId>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.eclipse.jetty</groupId>
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
<artifactId>jetty-servlet</artifactId>
|
<artifactId>jetty-servlet</artifactId>
|
||||||
|
|
|
@ -0,0 +1,64 @@
|
||||||
|
package ctbrec.ui.action;
|
||||||
|
|
||||||
|
import java.awt.Graphics2D;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
|
||||||
|
import javax.imageio.ImageIO;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import ctbrec.Config;
|
||||||
|
import ctbrec.Model;
|
||||||
|
import ctbrec.event.EventBusHolder;
|
||||||
|
import javafx.scene.Node;
|
||||||
|
|
||||||
|
public abstract class AbstractPortraitAction {
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(AbstractPortraitAction.class);
|
||||||
|
public static final String FORMAT = "jpg";
|
||||||
|
|
||||||
|
protected Node source;
|
||||||
|
protected Model model;
|
||||||
|
|
||||||
|
protected BufferedImage convertToScaledJpg(BufferedImage original) {
|
||||||
|
java.awt.Image scaledPortrait = original.getScaledInstance(-1, 256, java.awt.Image.SCALE_SMOOTH);
|
||||||
|
BufferedImage bimage = new BufferedImage(scaledPortrait.getWidth(null), scaledPortrait.getHeight(null), BufferedImage.TYPE_INT_RGB);
|
||||||
|
Graphics2D bGr = bimage.createGraphics();
|
||||||
|
bGr.drawImage(scaledPortrait, 0, 0, null);
|
||||||
|
bGr.dispose();
|
||||||
|
return bimage;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean copyToCacheAsJpg(String portraitId, BufferedImage portrait) throws IOException {
|
||||||
|
File output = getPortraitFile(portraitId);
|
||||||
|
Files.createDirectories(output.getParentFile().toPath());
|
||||||
|
LOG.debug("Writing scaled portrait to {}", output);
|
||||||
|
return ImageIO.write(portrait, FORMAT, output);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected File getPortraitFile(String portraitId) {
|
||||||
|
File configDir = Config.getInstance().getConfigDir();
|
||||||
|
File portraitDir = new File(configDir, "portraits");
|
||||||
|
File output = new File(portraitDir, portraitId + '.' + FORMAT);
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void firePortraitChanged() {
|
||||||
|
EventBusHolder.BUS.post(new PortraitChangedEvent(model));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class PortraitChangedEvent {
|
||||||
|
private Model mdl;
|
||||||
|
|
||||||
|
public PortraitChangedEvent(Model model) {
|
||||||
|
this.mdl = model;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Model getModel() {
|
||||||
|
return mdl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,7 +1,5 @@
|
||||||
package ctbrec.ui.action;
|
package ctbrec.ui.action;
|
||||||
|
|
||||||
import java.awt.Graphics2D;
|
|
||||||
import java.awt.Image;
|
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
@ -27,12 +25,9 @@ import javafx.scene.Node;
|
||||||
import javafx.scene.control.Label;
|
import javafx.scene.control.Label;
|
||||||
import javafx.scene.layout.GridPane;
|
import javafx.scene.layout.GridPane;
|
||||||
|
|
||||||
public class SetPortraitAction {
|
public class SetPortraitAction extends AbstractPortraitAction {
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(SetPortraitAction.class);
|
private static final Logger LOG = LoggerFactory.getLogger(SetPortraitAction.class);
|
||||||
public static final String FORMAT = "jpg";
|
|
||||||
|
|
||||||
private Node source;
|
|
||||||
private Model model;
|
|
||||||
private Consumer<Model> callback;
|
private Consumer<Model> callback;
|
||||||
|
|
||||||
public SetPortraitAction(Node source, Model selectedModel, Consumer<Model> callback) {
|
public SetPortraitAction(Node source, Model selectedModel, Consumer<Model> callback) {
|
||||||
|
@ -69,6 +64,7 @@ public class SetPortraitAction {
|
||||||
Config.getInstance().getSettings().modelPortraits.put(model.getUrl(), portraitId);
|
Config.getInstance().getSettings().modelPortraits.put(model.getUrl(), portraitId);
|
||||||
try {
|
try {
|
||||||
Config.getInstance().save();
|
Config.getInstance().save();
|
||||||
|
firePortraitChanged();
|
||||||
runCallback();
|
runCallback();
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Dialogs.showError("Set Portrait", "Couldn't change portrait image: ", e);
|
Dialogs.showError("Set Portrait", "Couldn't change portrait image: ", e);
|
||||||
|
@ -104,7 +100,8 @@ public class SetPortraitAction {
|
||||||
|
|
||||||
private boolean processImageFile(String portraitId, String selectedFile) {
|
private boolean processImageFile(String portraitId, String selectedFile) {
|
||||||
try {
|
try {
|
||||||
BufferedImage portrait = convertToScaledJpg(selectedFile);
|
BufferedImage original = ImageIO.read(new File(selectedFile));
|
||||||
|
BufferedImage portrait = convertToScaledJpg(original);
|
||||||
boolean success = copyToCacheAsJpg(portraitId, portrait);
|
boolean success = copyToCacheAsJpg(portraitId, portrait);
|
||||||
if (!success) {
|
if (!success) {
|
||||||
LOG.debug("Available formats: {}", Arrays.toString(ImageIO.getWriterFormatNames()));
|
LOG.debug("Available formats: {}", Arrays.toString(ImageIO.getWriterFormatNames()));
|
||||||
|
@ -117,28 +114,4 @@ public class SetPortraitAction {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private BufferedImage convertToScaledJpg(String file) throws IOException {
|
|
||||||
BufferedImage portrait = ImageIO.read(new File(file));
|
|
||||||
Image scaledPortrait = portrait.getScaledInstance(-1, 256, Image.SCALE_SMOOTH);
|
|
||||||
BufferedImage bimage = new BufferedImage(scaledPortrait.getWidth(null), scaledPortrait.getHeight(null), BufferedImage.TYPE_INT_RGB);
|
|
||||||
Graphics2D bGr = bimage.createGraphics();
|
|
||||||
bGr.drawImage(scaledPortrait, 0, 0, null);
|
|
||||||
bGr.dispose();
|
|
||||||
return bimage;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean copyToCacheAsJpg(String portraitId, BufferedImage portrait) throws IOException {
|
|
||||||
File output = getPortraitFile(portraitId);
|
|
||||||
Files.createDirectories(output.getParentFile().toPath());
|
|
||||||
LOG.debug("Writing scaled portrait to {}", output);
|
|
||||||
return ImageIO.write(portrait, FORMAT, output);
|
|
||||||
}
|
|
||||||
|
|
||||||
private File getPortraitFile(String portraitId) {
|
|
||||||
File configDir = Config.getInstance().getConfigDir();
|
|
||||||
File portraitDir = new File(configDir, "portraits");
|
|
||||||
File output = new File(portraitDir, portraitId + '.' + FORMAT);
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,71 @@
|
||||||
|
package ctbrec.ui.action;
|
||||||
|
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import ctbrec.Config;
|
||||||
|
import ctbrec.Model;
|
||||||
|
import ctbrec.ui.controls.Dialogs;
|
||||||
|
import javafx.embed.swing.SwingFXUtils;
|
||||||
|
import javafx.scene.Cursor;
|
||||||
|
import javafx.scene.Node;
|
||||||
|
import javafx.scene.image.Image;
|
||||||
|
|
||||||
|
public class SetThumbAsPortraitAction extends AbstractPortraitAction {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(SetThumbAsPortraitAction.class);
|
||||||
|
|
||||||
|
private Image image;
|
||||||
|
|
||||||
|
public SetThumbAsPortraitAction(Node source, Model model, Image image) {
|
||||||
|
this.source = source;
|
||||||
|
this.model = model;
|
||||||
|
this.image = image;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void execute() {
|
||||||
|
source.setCursor(Cursor.WAIT);
|
||||||
|
try {
|
||||||
|
BufferedImage bufferedImage = convertFxImageToAwt(image);
|
||||||
|
BufferedImage croppedImage = cropImage(bufferedImage);
|
||||||
|
BufferedImage portrait = convertToScaledJpg(croppedImage);
|
||||||
|
String portraitId = UUID.nameUUIDFromBytes(model.getUrl().getBytes(StandardCharsets.UTF_8)).toString();
|
||||||
|
copyToCacheAsJpg(portraitId, portrait);
|
||||||
|
Config.getInstance().getSettings().modelPortraits.put(model.getUrl(), portraitId);
|
||||||
|
Config.getInstance().save();
|
||||||
|
firePortraitChanged();
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOG.error("Error while changing portrait image", e);
|
||||||
|
Dialogs.showError("Set Portrait", "Couldn't change portrait image: ", e);
|
||||||
|
}
|
||||||
|
source.setCursor(Cursor.DEFAULT);
|
||||||
|
}
|
||||||
|
|
||||||
|
private BufferedImage cropImage(BufferedImage img) {
|
||||||
|
int w = img.getWidth();
|
||||||
|
int h = img.getHeight();
|
||||||
|
if (w > h) {
|
||||||
|
return cropSides(img);
|
||||||
|
} else {
|
||||||
|
return cropTopAndBottom(img);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private BufferedImage cropSides(BufferedImage img) {
|
||||||
|
int overlap = img.getWidth() - img.getHeight();
|
||||||
|
return img.getSubimage(overlap / 2, 0, img.getHeight(), img.getHeight());
|
||||||
|
}
|
||||||
|
|
||||||
|
private BufferedImage cropTopAndBottom(BufferedImage img) {
|
||||||
|
int overlap = img.getHeight() - img.getWidth();
|
||||||
|
return img.getSubimage(0, overlap/2, img.getWidth(), img.getWidth());
|
||||||
|
}
|
||||||
|
|
||||||
|
private BufferedImage convertFxImageToAwt(Image img) {
|
||||||
|
return SwingFXUtils.fromFXImage(img, null);
|
||||||
|
}
|
||||||
|
}
|
|
@ -144,7 +144,7 @@ public class ModelMenuContributor {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addPortrait(ContextMenu menu, List<Model> selectedModels) {
|
private void addPortrait(ContextMenu menu, List<Model> selectedModels) {
|
||||||
var portrait = new MenuItem("Portrait");
|
var portrait = new MenuItem("Select Portrait");
|
||||||
portrait.setDisable(selectedModels.size() != 1);
|
portrait.setDisable(selectedModels.size() != 1);
|
||||||
portrait.setOnAction(e -> new SetPortraitAction(source, selectedModels.get(0), portraitCallback).execute());
|
portrait.setOnAction(e -> new SetPortraitAction(source, selectedModels.get(0), portraitCallback).execute());
|
||||||
menu.getItems().add(portrait);
|
menu.getItems().add(portrait);
|
||||||
|
|
|
@ -33,6 +33,7 @@ import ctbrec.ui.AutosizeAlert;
|
||||||
import ctbrec.ui.DesktopIntegration;
|
import ctbrec.ui.DesktopIntegration;
|
||||||
import ctbrec.ui.SiteUiFactory;
|
import ctbrec.ui.SiteUiFactory;
|
||||||
import ctbrec.ui.TokenLabel;
|
import ctbrec.ui.TokenLabel;
|
||||||
|
import ctbrec.ui.action.SetThumbAsPortraitAction;
|
||||||
import ctbrec.ui.controls.CustomMouseBehaviorContextMenu;
|
import ctbrec.ui.controls.CustomMouseBehaviorContextMenu;
|
||||||
import ctbrec.ui.controls.FasterVerticalScrollPaneSkin;
|
import ctbrec.ui.controls.FasterVerticalScrollPaneSkin;
|
||||||
import ctbrec.ui.controls.SearchBox;
|
import ctbrec.ui.controls.SearchBox;
|
||||||
|
@ -479,9 +480,13 @@ public class ThumbOverviewTab extends Tab implements TabSelectionListener {
|
||||||
.afterwards(() -> selectedModels.forEach(m -> getThumbCell(m).ifPresent(ThumbCell::update)))
|
.afterwards(() -> selectedModels.forEach(m -> getThumbCell(m).ifPresent(ThumbCell::update)))
|
||||||
.contributeToMenu(selectedModels, contextMenu);
|
.contributeToMenu(selectedModels, contextMenu);
|
||||||
|
|
||||||
|
var useImageAsPortrait = new MenuItem("Use As Portrait");
|
||||||
|
useImageAsPortrait.setOnAction(e -> new SetThumbAsPortraitAction(getTabPane(), cell.getModel(), cell.getImage()).execute());
|
||||||
|
|
||||||
var refresh = new MenuItem("Refresh Overview");
|
var refresh = new MenuItem("Refresh Overview");
|
||||||
refresh.setOnAction(e -> refresh());
|
refresh.setOnAction(e -> refresh());
|
||||||
contextMenu.getItems().addAll(refresh);
|
|
||||||
|
contextMenu.getItems().addAll(useImageAsPortrait, refresh);
|
||||||
var model = cell.getModel();
|
var model = cell.getModel();
|
||||||
if (model instanceof MyFreeCamsModel && Objects.equals(System.getenv("CTBREC_DEV"), "1")) {
|
if (model instanceof MyFreeCamsModel && Objects.equals(System.getenv("CTBREC_DEV"), "1")) {
|
||||||
var debug = new MenuItem("debug");
|
var debug = new MenuItem("debug");
|
||||||
|
|
|
@ -20,15 +20,18 @@ import org.slf4j.LoggerFactory;
|
||||||
import com.google.common.cache.CacheBuilder;
|
import com.google.common.cache.CacheBuilder;
|
||||||
import com.google.common.cache.CacheLoader;
|
import com.google.common.cache.CacheLoader;
|
||||||
import com.google.common.cache.LoadingCache;
|
import com.google.common.cache.LoadingCache;
|
||||||
|
import com.google.common.eventbus.Subscribe;
|
||||||
|
|
||||||
import ctbrec.Config;
|
import ctbrec.Config;
|
||||||
import ctbrec.Model;
|
import ctbrec.Model;
|
||||||
import ctbrec.StringUtil;
|
import ctbrec.StringUtil;
|
||||||
|
import ctbrec.event.EventBusHolder;
|
||||||
import ctbrec.recorder.Recorder;
|
import ctbrec.recorder.Recorder;
|
||||||
import ctbrec.sites.Site;
|
import ctbrec.sites.Site;
|
||||||
import ctbrec.ui.AutosizeAlert;
|
import ctbrec.ui.AutosizeAlert;
|
||||||
import ctbrec.ui.JavaFxModel;
|
import ctbrec.ui.JavaFxModel;
|
||||||
import ctbrec.ui.PreviewPopupHandler;
|
import ctbrec.ui.PreviewPopupHandler;
|
||||||
|
import ctbrec.ui.action.AbstractPortraitAction.PortraitChangedEvent;
|
||||||
import ctbrec.ui.action.PlayAction;
|
import ctbrec.ui.action.PlayAction;
|
||||||
import ctbrec.ui.action.SetPortraitAction;
|
import ctbrec.ui.action.SetPortraitAction;
|
||||||
import ctbrec.ui.controls.CustomMouseBehaviorContextMenu;
|
import ctbrec.ui.controls.CustomMouseBehaviorContextMenu;
|
||||||
|
@ -107,6 +110,19 @@ public abstract class AbstractRecordedModelsTab extends Tab implements TabSelect
|
||||||
|
|
||||||
AbstractRecordedModelsTab(String text) {
|
AbstractRecordedModelsTab(String text) {
|
||||||
super(text);
|
super(text);
|
||||||
|
registerPortraitListener();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void registerPortraitListener() {
|
||||||
|
EventBusHolder.BUS.register(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Subscribe
|
||||||
|
public void portraitChanged(PortraitChangedEvent e) {
|
||||||
|
portraitCache.invalidate(e.getModel());
|
||||||
|
if (table != null) {
|
||||||
|
table.refresh();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void createGui() {
|
protected void createGui() {
|
||||||
|
|
|
@ -107,6 +107,11 @@
|
||||||
<artifactId>javafx-media</artifactId>
|
<artifactId>javafx-media</artifactId>
|
||||||
<version>${version.javafx}</version>
|
<version>${version.javafx}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.openjfx</groupId>
|
||||||
|
<artifactId>javafx-swing</artifactId>
|
||||||
|
<version>${version.javafx}</version>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.google.guava</groupId>
|
<groupId>com.google.guava</groupId>
|
||||||
<artifactId>guava</artifactId>
|
<artifactId>guava</artifactId>
|
||||||
|
|
Loading…
Reference in New Issue