Merge branch 'main' into deleteZone
This commit is contained in:
commit
661524d675
31 files changed
+1100
-466
No files matched your search
@@ -1,56 +0,0 @@
|
||||
package pdl.backend;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
|
||||
public class Algorithm {
|
||||
private final String name;
|
||||
private final String path;
|
||||
private final ArrayList<Parameter> parameters;
|
||||
|
||||
public Algorithm(String name, String path) {
|
||||
this.name = name;
|
||||
this.path = path;
|
||||
this.parameters = new ArrayList<>();
|
||||
switch (path) {
|
||||
case "changeLuminosity":
|
||||
Parameter param1 = new Parameter("delta", "delta", -255, 255, 1);
|
||||
this.parameters.add(param1);
|
||||
break;
|
||||
case "histogram":
|
||||
break;
|
||||
case "colorFilter":
|
||||
Parameter param2 = new Parameter("hue", "Teinte", 0, 359, 1);
|
||||
this.parameters.add(param2);
|
||||
break;
|
||||
case "meanFilter":
|
||||
Parameter param3 = new Parameter("size", "Taille du filtre", 1, 101, 2);
|
||||
this.parameters.add(param3);
|
||||
break;
|
||||
case "gaussienFilter":
|
||||
// TODO
|
||||
// Parameter param4 = new Parameter("size", "Taille du filtre", 1, 101, 2);
|
||||
// this.parameters.add(param4);
|
||||
break;
|
||||
case "contours":
|
||||
break;
|
||||
default:
|
||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Invalid path");
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public String getPath() {
|
||||
return this.path;
|
||||
}
|
||||
|
||||
public ArrayList<Parameter> getParameters() {
|
||||
return this.parameters;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package pdl.backend.Algorithm;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
|
||||
import boofcv.struct.image.GrayU8;
|
||||
import boofcv.struct.image.Planar;
|
||||
import pdl.backend.Algorithm.Parameters.Parameter;
|
||||
import pdl.backend.Algorithm.Parameters.ParameterNullPointerException;
|
||||
|
||||
public class Algorithm {
|
||||
private final String name;
|
||||
private final String path;
|
||||
private final List<Parameter> parameters;
|
||||
private final AlgorithmFunctionInterface function;
|
||||
|
||||
public Algorithm(String name, String path, List<Parameter> parameters, AlgorithmFunctionInterface function) {
|
||||
this.name = name;
|
||||
this.path = path;
|
||||
this.parameters = parameters;
|
||||
this.function = function;
|
||||
}
|
||||
|
||||
public void apply(Planar<GrayU8> image) {
|
||||
List<Object> para = this.parameters.stream().map(p -> p.getValue()).toList();
|
||||
for (Object p : para) {
|
||||
if (p == null) {
|
||||
throw new ParameterNullPointerException("Parameter not set");
|
||||
}
|
||||
}
|
||||
this.function.apply(image, para);
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public String getPath() {
|
||||
return this.path;
|
||||
}
|
||||
|
||||
public List<Parameter> getParameters() {
|
||||
return this.parameters;
|
||||
}
|
||||
|
||||
public ObjectNode getNode(ObjectMapper mapper) {
|
||||
ObjectNode node = mapper.createObjectNode();
|
||||
node.put("name", this.getName());
|
||||
node.put("path", this.getPath());
|
||||
ArrayNode parameters = node.putArray("parameters");
|
||||
for (Parameter p : this.getParameters()) {
|
||||
parameters.add(p.getNode(mapper));
|
||||
}
|
||||
return node;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package pdl.backend.Algorithm;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import boofcv.struct.image.GrayU8;
|
||||
import boofcv.struct.image.Planar;
|
||||
|
||||
public interface AlgorithmFunctionInterface {
|
||||
void apply(Planar<GrayU8> image, List<Object> parameters);
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package pdl.backend.Algorithm.Parameters;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
|
||||
public class DoubleParameter extends Parameter {
|
||||
private final double min;
|
||||
private final double max;
|
||||
private final double step;
|
||||
|
||||
public DoubleParameter(String name, String displayName, double min, double max, double step) {
|
||||
super(name, displayName, "range");
|
||||
this.max = max;
|
||||
this.min = min;
|
||||
this.step = step;
|
||||
}
|
||||
|
||||
public double getMin() {
|
||||
return this.min;
|
||||
}
|
||||
|
||||
public double getMax() {
|
||||
return this.max;
|
||||
}
|
||||
|
||||
public double getStep() {
|
||||
return this.step;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValue(String value) {
|
||||
super.setValueObject(Double.parseDouble(value));
|
||||
}
|
||||
|
||||
public ObjectNode getNode(ObjectMapper mapper) {
|
||||
ObjectNode node = super.getNode(mapper);
|
||||
node.put("min", this.min);
|
||||
node.put("max", this.max);
|
||||
node.put("step", this.step);
|
||||
return node;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package pdl.backend.Algorithm.Parameters;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
|
||||
public class IntegerParameter extends Parameter {
|
||||
private final int min;
|
||||
private final int max;
|
||||
private final int step;
|
||||
|
||||
public IntegerParameter(String name, String displayName, int min, int max, int step) {
|
||||
super(name, displayName, "range");
|
||||
this.max = max;
|
||||
this.min = min;
|
||||
this.step = step;
|
||||
}
|
||||
|
||||
public int getMin() {
|
||||
return this.min;
|
||||
}
|
||||
|
||||
public int getMax() {
|
||||
return this.max;
|
||||
}
|
||||
|
||||
public int getStep() {
|
||||
return this.step;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValue(String value) {
|
||||
super.setValueObject(Integer.parseInt(value));
|
||||
}
|
||||
|
||||
public ObjectNode getNode(ObjectMapper mapper) {
|
||||
ObjectNode node = super.getNode(mapper);
|
||||
node.put("min", this.min);
|
||||
node.put("max", this.max);
|
||||
node.put("step", this.step);
|
||||
return node;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package pdl.backend.Algorithm.Parameters;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
|
||||
public abstract class Parameter {
|
||||
private final String name;
|
||||
private final String displayName;
|
||||
private final String type;
|
||||
private Object value;
|
||||
|
||||
public Parameter(String name, String displayName, String type) {
|
||||
this.name = name;
|
||||
this.displayName = displayName;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public String getDisplayName() {
|
||||
return this.displayName;
|
||||
}
|
||||
|
||||
public String getType() {
|
||||
return this.type;
|
||||
}
|
||||
|
||||
public ObjectNode getNode(ObjectMapper mapper) {
|
||||
ObjectNode node = mapper.createObjectNode();
|
||||
node.put("name", this.name);
|
||||
node.put("displayName", this.displayName);
|
||||
node.put("type", this.type);
|
||||
return node;
|
||||
}
|
||||
|
||||
public void setValueObject(Object value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public void setValue(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public Object getValue() {
|
||||
return this.value;
|
||||
}
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
package pdl.backend.Algorithm.Parameters;
|
||||
|
||||
public class ParameterNullPointerException extends NullPointerException {
|
||||
public ParameterNullPointerException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package pdl.backend.Algorithm.Parameters;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
|
||||
public class SelectParameter extends Parameter {
|
||||
private final String[] options;
|
||||
|
||||
public SelectParameter(String name, String displayName, String[] options) {
|
||||
super(name, displayName, "select");
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
public String[] getOptions() {
|
||||
return this.options;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValue(String value) {
|
||||
super.setValueObject(value);
|
||||
}
|
||||
|
||||
public ObjectNode getNode(ObjectMapper mapper) {
|
||||
ObjectNode node = super.getNode(mapper);
|
||||
ArrayNode optionsNode = mapper.createArrayNode();
|
||||
for (String option : this.options) {
|
||||
optionsNode.add(option);
|
||||
}
|
||||
node.set("options", optionsNode);
|
||||
return node;
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
package pdl.backend;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
public class AlgorithmController {
|
||||
|
||||
@Autowired
|
||||
private ObjectMapper mapper;
|
||||
|
||||
@GetMapping(value = "/algorithms", produces = "application/json")
|
||||
@ResponseBody
|
||||
public ArrayNode getAlgorithmList() {
|
||||
ArrayList<Algorithm> algorithms = new ArrayList<Algorithm>();
|
||||
Algorithm algo = new Algorithm("Changement de luminosité", "changeLuminosity");
|
||||
algorithms.add(algo);
|
||||
algo = new Algorithm("Changement de teinte", "colorFilter");
|
||||
algorithms.add(algo);
|
||||
algo = new Algorithm("Filtre Moyenneur", "meanFilter");
|
||||
algorithms.add(algo);
|
||||
algo = new Algorithm("Filtre Gaussien", "gaussienFilter");
|
||||
algorithms.add(algo);
|
||||
algo = new Algorithm("Détection de contours", "contours");
|
||||
algorithms.add(algo);
|
||||
algo = new Algorithm("Égalisation d'histogramme", "histogram");
|
||||
algorithms.add(algo);
|
||||
|
||||
ArrayNode nodes = mapper.createArrayNode();
|
||||
for (Algorithm a : algorithms) {
|
||||
ObjectNode objectNode = mapper.createObjectNode();
|
||||
objectNode.put("name", a.getName());
|
||||
objectNode.put("path", a.getPath());
|
||||
ArrayNode parameters = objectNode.putArray("parameters");
|
||||
for (Parameter p : a.getParameters()) {
|
||||
ObjectNode paramNode = mapper.createObjectNode();
|
||||
paramNode.put("name", p.getName());
|
||||
paramNode.put("displayName", p.getDisplayName());
|
||||
paramNode.put("min", p.getMin());
|
||||
paramNode.put("max", p.getMax());
|
||||
paramNode.put("step", p.getStep());
|
||||
parameters.add(paramNode);
|
||||
}
|
||||
nodes.add(objectNode);
|
||||
}
|
||||
return nodes;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
package pdl.backend.Controller;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||
|
||||
import boofcv.struct.border.BorderType;
|
||||
import boofcv.struct.image.GrayU8;
|
||||
import boofcv.struct.image.Planar;
|
||||
import pdl.backend.Algorithm.Algorithm;
|
||||
import pdl.backend.Algorithm.Parameters.DoubleParameter;
|
||||
import pdl.backend.Algorithm.Parameters.IntegerParameter;
|
||||
import pdl.backend.Algorithm.Parameters.SelectParameter;
|
||||
import pdl.backend.Image.ImageProcessing;
|
||||
import pdl.backend.Algorithm.Parameters.Parameter;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
public class AlgorithmController {
|
||||
|
||||
@Autowired
|
||||
private ObjectMapper mapper;
|
||||
|
||||
public static List<Algorithm> algorithmList() {
|
||||
final String[] borderTypes = { "SKIP", "ZERO", "NORMALIZED", "REFLECT", "EXTENDED", "WRAP" };
|
||||
ArrayList<Algorithm> algorithms = new ArrayList<Algorithm>();
|
||||
algorithms.add(new Algorithm("Changement de luminosité", "changeLuminosity",
|
||||
Arrays.asList(new IntegerParameter("delta", "delta", -255, 255, 1)),
|
||||
(Planar<GrayU8> input, List<Object> para) -> {
|
||||
ImageProcessing.changeLuminosity(input, (int) para.get(0));
|
||||
}));
|
||||
algorithms.add(new Algorithm("Changement de teinte", "colorFilter",
|
||||
Arrays.asList(new IntegerParameter("hue", "Teinte", 0, 359, 1)),
|
||||
(Planar<GrayU8> input, List<Object> para) -> {
|
||||
ImageProcessing.colorFilter(input, (int) para.get(0));
|
||||
}));
|
||||
|
||||
// TODO: TESTS
|
||||
algorithms.add(new Algorithm("Filtre Moyenneur", "meanFilter", Arrays.asList(
|
||||
new IntegerParameter("size", "Taille du filtre", 1, 21, 2),
|
||||
new SelectParameter("borderType", "Type de bordure", borderTypes)),
|
||||
(Planar<GrayU8> input, List<Object> para) -> {
|
||||
ImageProcessing.meanFilter(input, (int) para.get(0), BorderType.valueOf((String) para.get(1)));
|
||||
}));
|
||||
|
||||
// TODO: TESTS
|
||||
algorithms.add(new Algorithm("Filtre Gaussien", "gaussienFilter", Arrays.asList(
|
||||
new IntegerParameter("size", "Taille du filtre", 1, 21, 2),
|
||||
new DoubleParameter("sigma", "Sigma", 0.1, 2, 0.1),
|
||||
new SelectParameter("borderType", "Type de bordure", borderTypes)),
|
||||
(Planar<GrayU8> input, List<Object> para) -> {
|
||||
ImageProcessing.gaussienFilter(input, (int) para.get(0), (double) para.get(1),
|
||||
BorderType.valueOf((String) para.get(2)));
|
||||
}));
|
||||
|
||||
algorithms.add(new Algorithm("Détection de contours", "contours", new ArrayList<Parameter>(),
|
||||
(Planar<GrayU8> input, List<Object> para) -> {
|
||||
ImageProcessing.gradientImageSobel(input);
|
||||
}));
|
||||
|
||||
algorithms.add(new Algorithm("Égalisation d'histogramme", "histogram", new ArrayList<Parameter>(),
|
||||
(Planar<GrayU8> input, List<Object> para) -> {
|
||||
ImageProcessing.histogram(input);
|
||||
}));
|
||||
|
||||
return algorithms;
|
||||
}
|
||||
|
||||
@GetMapping(value = "/algorithms", produces = "application/json")
|
||||
@ResponseBody
|
||||
public ArrayNode getAlgorithmList() {
|
||||
List<Algorithm> algorithms = AlgorithmController.algorithmList();
|
||||
ArrayNode nodes = mapper.createArrayNode();
|
||||
for (Algorithm a : algorithms) {
|
||||
nodes.add(a.getNode(mapper));
|
||||
}
|
||||
return nodes;
|
||||
}
|
||||
|
||||
}
|
||||
+37
-71
@@ -1,4 +1,4 @@
|
||||
package pdl.backend;
|
||||
package pdl.backend.Controller;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
@@ -16,8 +16,13 @@ import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
|
||||
import boofcv.io.image.ConvertBufferedImage;
|
||||
import boofcv.struct.border.BorderType;
|
||||
import boofcv.struct.image.GrayU8;
|
||||
import boofcv.struct.image.Planar;
|
||||
import pdl.backend.Algorithm.Algorithm;
|
||||
import pdl.backend.Algorithm.Parameters.Parameter;
|
||||
import pdl.backend.Image.Image;
|
||||
import pdl.backend.Image.ImageDao;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.core.io.InputStreamResource;
|
||||
@@ -135,6 +140,23 @@ public class ImageController {
|
||||
return nodes;
|
||||
}
|
||||
|
||||
public BorderType getBorderType(String borderType) {
|
||||
switch (borderType) {
|
||||
case "EXTENDED":
|
||||
return BorderType.EXTENDED;
|
||||
case "REFLECT":
|
||||
return BorderType.REFLECT;
|
||||
case "REFLECT_101":
|
||||
return BorderType.NORMALIZED;
|
||||
case "WRAP":
|
||||
return BorderType.WRAP;
|
||||
case "ZERO":
|
||||
return BorderType.ZERO;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* récupère une image et la modifie si un algorithme est renseigné
|
||||
*
|
||||
@@ -159,6 +181,7 @@ public class ImageController {
|
||||
return ResponseEntity.ok().contentType(image.getMediaType())
|
||||
.body(new InputStreamResource(inputStream));
|
||||
}
|
||||
final List<Algorithm> algos = AlgorithmController.algorithmList();
|
||||
// Vérifie la présence du paramètre "algorithm"
|
||||
if (!parameters.containsKey("algorithm")) {
|
||||
return JSONError("First parameter should be 'algorithm'", HttpStatus.BAD_REQUEST);
|
||||
@@ -173,82 +196,25 @@ public class ImageController {
|
||||
}
|
||||
Planar<GrayU8> input = ConvertBufferedImage.convertFromPlanar(bufImg, null, true, GrayU8.class);
|
||||
|
||||
// applique l'algo a l'image en fonction des parametres et verifie la validite
|
||||
// des parametres
|
||||
switch (algo) {
|
||||
case "changeLuminosity":
|
||||
if (!parameters.containsKey("delta") || parameters.size() != 2)
|
||||
return JSONError("Algorithm 'changeLuminosity' requires one parameter 'delta'(integer)",
|
||||
HttpStatus.BAD_REQUEST);
|
||||
for (Algorithm a : algos) {
|
||||
if (a.getPath().equals(algo)) {
|
||||
for (Parameter p : a.getParameters()) {
|
||||
if (!parameters.containsKey(p.getName())) {
|
||||
return JSONError("Parameter '" + p.getName() + "' is missing", HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
try {
|
||||
int i = Integer.parseInt(parameters.get("delta"));
|
||||
ImageProcessing.changeLuminosity(input, i);
|
||||
} catch (NumberFormatException e) {
|
||||
return JSONError("Parameter 'delta' should be an integer", HttpStatus.BAD_REQUEST);
|
||||
p.setValue(parameters.get(p.getName()));
|
||||
} catch (Exception e) {
|
||||
return JSONError("Parameter '" + p.getName() + "' is invalid", HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "histogram":
|
||||
if (parameters.size() != 1)
|
||||
return JSONError("Algorithm 'histogram' doesn't require any parameters", HttpStatus.BAD_REQUEST);
|
||||
ImageProcessing.histogram(input);
|
||||
break;
|
||||
case "colorFilter":
|
||||
if (!parameters.containsKey("hue") || parameters.size() != 2)
|
||||
return JSONError("Algorithm 'colorFilter' requires one parameter 'hue'(integer)",
|
||||
HttpStatus.BAD_REQUEST);
|
||||
try {
|
||||
int i = Integer.parseInt(parameters.get("hue"));
|
||||
if (i < 0) {
|
||||
return JSONError("Parameter 'hue' should be an integer between 0 and 360",
|
||||
HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
ImageProcessing.colorFilter(i, input);
|
||||
// hue entre 0 et 360?
|
||||
} catch (NumberFormatException e) {
|
||||
return JSONError("Parameter 'hue' should be an integer between 0 and 360",
|
||||
HttpStatus.BAD_REQUEST);
|
||||
a.apply(input);
|
||||
} catch (Exception e) {
|
||||
return JSONError("There was an internal server error", HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
break;
|
||||
case "meanFilter":
|
||||
if (!parameters.containsKey("size") || parameters.size() != 2)
|
||||
return JSONError("Algorithm 'meanFilter' requires one parameter 'size'(positive odd integer)",
|
||||
HttpStatus.BAD_REQUEST);
|
||||
try {
|
||||
int i = Integer.parseInt(parameters.get("size"));
|
||||
if (i % 2 == 0)
|
||||
return JSONError("Parameter 'size' should be a positive odd integer",
|
||||
HttpStatus.BAD_REQUEST);
|
||||
Planar<GrayU8> clone = input.clone();
|
||||
ImageProcessing.meanFilter(clone, input, i);
|
||||
} catch (NumberFormatException e) {
|
||||
return JSONError("Parameter 'size' should be a positive odd integer",
|
||||
HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
break;
|
||||
case "gaussienFilter":
|
||||
if (/* !parameters.containsKey("size") || */ parameters.size() != 1)
|
||||
return JSONError("Algorithm 'gaussienFilter' requires no parameters", HttpStatus.BAD_REQUEST);
|
||||
try {
|
||||
// int i = Integer.parseInt(parameters.get("size"));
|
||||
// if (i % 2 == 0)
|
||||
// return JSONError("Parameter 'size' should be a positive odd integer",
|
||||
// HttpStatus.BAD_REQUEST);
|
||||
Planar<GrayU8> clone = input.clone();
|
||||
ImageProcessing.gaussienFilter(clone, input, 5);
|
||||
} catch (NumberFormatException e) {
|
||||
return JSONError("Parameter 'size' should be a positive odd integer",
|
||||
HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
break;
|
||||
case "contours":
|
||||
if (parameters.size() != 1)
|
||||
return JSONError("Algorithm 'contours' doesn't require any parameters", HttpStatus.BAD_REQUEST);
|
||||
Planar<GrayU8> clone = input.clone();
|
||||
ImageProcessing.gradientImageSobel(clone, input);
|
||||
break;
|
||||
default:
|
||||
return JSONError("Requested algorithm doesn't exist", HttpStatus.BAD_REQUEST);
|
||||
|
||||
}
|
||||
|
||||
// transforme l'image en quelque chose que le navigateur puisse lire
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package pdl.backend;
|
||||
package pdl.backend.Image;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.ByteArrayInputStream;
|
||||
+4
-2
@@ -1,4 +1,4 @@
|
||||
package pdl.backend;
|
||||
package pdl.backend.Image;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
@@ -13,6 +13,8 @@ import java.util.Optional;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import pdl.backend.Dao;
|
||||
|
||||
@Repository
|
||||
public class ImageDao implements Dao<Image> {
|
||||
|
||||
@@ -21,7 +23,7 @@ public class ImageDao implements Dao<Image> {
|
||||
private final Map<Long, Image> images = new HashMap<>();
|
||||
|
||||
public ImageDao() throws IOException {
|
||||
this("images");
|
||||
this("/images");
|
||||
}
|
||||
|
||||
public ImageDao(final String folderPathStr) throws IOException {
|
||||
+125
-73
@@ -1,5 +1,6 @@
|
||||
package pdl.backend;
|
||||
package pdl.backend.Image;
|
||||
|
||||
import boofcv.struct.border.BorderType;
|
||||
import boofcv.struct.image.GrayU8;
|
||||
import boofcv.struct.image.Planar;
|
||||
|
||||
@@ -9,7 +10,10 @@ import java.util.Random;
|
||||
|
||||
import boofcv.concurrency.BoofConcurrency;
|
||||
|
||||
public class ImageProcessing {
|
||||
public final class ImageProcessing {
|
||||
|
||||
private ImageProcessing() {
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -47,14 +51,13 @@ public class ImageProcessing {
|
||||
* Convertit une image en couleur en une image en niveaux de gris
|
||||
*
|
||||
* @param input l'image d'entrée en couleur
|
||||
* @param output l'image de sortie en niveaux de gris
|
||||
*/
|
||||
public static void convertColorToGray(Planar<GrayU8> input, Planar<GrayU8> output) {
|
||||
public static void convertColorToGray(Planar<GrayU8> input) {
|
||||
BoofConcurrency.loopFor(0, input.height, y -> {
|
||||
for (int x = 0; x < input.width; x++) {
|
||||
int[] rgb = getRGBValue(input, x, y);
|
||||
int gray = (int) (0.3 * rgb[0] + 0.59 * rgb[1] + 0.11 * rgb[2]);
|
||||
setRGBValue(output, x, y, new int[] { gray, gray, gray });
|
||||
setRGBValue(input, x, y, new int[] { gray, gray, gray });
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -93,8 +96,8 @@ public class ImageProcessing {
|
||||
*/
|
||||
public static void histogram(Planar<GrayU8> input) {
|
||||
int h[] = new int[256];
|
||||
Planar<GrayU8> grayInput = input.createSameShape();
|
||||
convertColorToGray(input, grayInput);
|
||||
Planar<GrayU8> grayInput = input.clone();
|
||||
convertColorToGray(grayInput);
|
||||
// On remplit le tableau avec le nombre de pixels pour chaque niveau de
|
||||
// gris
|
||||
BoofConcurrency.loopFor(0, input.height, y -> {
|
||||
@@ -218,7 +221,7 @@ public class ImageProcessing {
|
||||
* @param hue La valeur de la teinte à appliquer
|
||||
* @param input L'image à modifier
|
||||
*/
|
||||
public static void colorFilter(int hue, Planar<GrayU8> input) {
|
||||
public static void colorFilter(Planar<GrayU8> input, int hue) {
|
||||
if (hue < 0 || hue > 360) {
|
||||
throw new IllegalArgumentException("Hue doit être compris entre 0 et 360");
|
||||
}
|
||||
@@ -241,14 +244,14 @@ public class ImageProcessing {
|
||||
* calculé sur input par convolution avec les filtres de Sobel.
|
||||
*
|
||||
* @param input L'image d'entrée en niveaux de gris.
|
||||
* @param output L'image de sortie en niveaux de gris.
|
||||
*/
|
||||
public static void gradientImageSobel(Planar<GrayU8> input, Planar<GrayU8> output) {
|
||||
public static void gradientImageSobel(Planar<GrayU8> input) {
|
||||
Planar<GrayU8> output = input.createSameShape();
|
||||
int h1[][] = { { -1, -2, -1 }, { 0, 0, 0 }, { 1, 2, 1 } };
|
||||
int h2[][] = { { -1, 0, 1 }, { -2, 0, 2 }, { -1, 0, 1 } };
|
||||
int half = h1.length / 2;
|
||||
Planar<GrayU8> inputGray = input.createSameShape();
|
||||
convertColorToGray(input, inputGray);
|
||||
Planar<GrayU8> inputGray = input.clone();
|
||||
convertColorToGray(inputGray);
|
||||
BoofConcurrency.loopFor(0, input.height, y -> {
|
||||
for (int x = 0; x < input.width; x++) {
|
||||
if (y < half || x < half || y >= input.height - half || x >= input.width - half) {
|
||||
@@ -279,78 +282,84 @@ public class ImageProcessing {
|
||||
setRGBValue(output, x, y, rgb);
|
||||
}
|
||||
});
|
||||
for (int i = 0; i < input.width; i++) {
|
||||
for (int j = 0; j < input.height; j++) {
|
||||
int[] rgb = getRGBValue(output, i, j);
|
||||
setRGBValue(input, i, j, rgb);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Applique un filtre moyenneur de taille indiquée à l'image
|
||||
* passée en paramètre (le bord de l'image n'est pas modifié)
|
||||
* passée en paramètre et stocke le résultat dans l'image de sortie.
|
||||
*
|
||||
* @param input image d'entrée
|
||||
* @param output image de sortie
|
||||
* @param size taille du filtre à appliquer (doit être impaire)
|
||||
* @param borderType type de bord à utiliser pour la convolution
|
||||
*/
|
||||
public static void meanFilter(Planar<GrayU8> input, Planar<GrayU8> output, int size) {
|
||||
public static void meanFilter(Planar<GrayU8> input, int size, BorderType borderType) {
|
||||
// Vérification que la taille est impaire
|
||||
if (size % 2 == 0) {
|
||||
throw new IllegalArgumentException("La taille doit être impaire");
|
||||
if (size % 2 == 0 || size < 0) {
|
||||
throw new IllegalArgumentException(
|
||||
"La taille doit être impaire et positive");
|
||||
}
|
||||
// Boucle pour parcourir tous les pixels de l'image d'entrée
|
||||
BoofConcurrency.loopFor(0, input.height, y -> {
|
||||
for (int x = 0; x < input.width; x++) {
|
||||
if (y < size / 2 || x < size / 2 || y >= input.height - size / 2
|
||||
|| x >= input.width - size / 2) {
|
||||
setRGBValue(output, x, y, getRGBValue(input, x, y));
|
||||
continue;
|
||||
}
|
||||
int totalValue[] = new int[3];
|
||||
// Boucle pour parcourir le carré size x size autour du pixel actuel
|
||||
for (int j = y - size / 2; j < y + size / 2 + 1; j++) {
|
||||
for (int i = x - size / 2; i < x + size / 2 + 1; i++) {
|
||||
for (int k = 0; k < totalValue.length; k++) {
|
||||
totalValue[k] += input.getBand(k).get(i, j);
|
||||
int[][] kernel = new int[size][size];
|
||||
for (int i = 0; i < size; i++) {
|
||||
for (int j = 0; j < size; j++) {
|
||||
kernel[i][j] = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Calcule la moyenne des pixels du carré et l'applique au pixel actuel dans
|
||||
// l'image de
|
||||
// sortie
|
||||
for (int k = 0; k < totalValue.length; k++) {
|
||||
totalValue[k] /= size * size;
|
||||
}
|
||||
setRGBValue(output, x, y, totalValue);
|
||||
}
|
||||
});
|
||||
|
||||
convolution(input, kernel, borderType);
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO
|
||||
* Calcule le noyau gaussien de taille demandée
|
||||
*
|
||||
* @param size taille du noyau gaussien
|
||||
* @return noyau gaussien
|
||||
* Applique un filtre gaussien de taille et de valeur données sur une image et
|
||||
* stocke le résultat dans l'image de sortie.
|
||||
*
|
||||
* @param input L'image d'entrée
|
||||
* @param size La taille du filtre à appliquer (doit être impaire)
|
||||
* @param sigma La valeur du sigma à utiliser pour le filtre gaussien.
|
||||
* @param borderType Le type de bord à utiliser pour la convolution
|
||||
*/
|
||||
public static int[][] createGaussienKernel(int size) {
|
||||
// Vérification que la taille est impaire
|
||||
if (size % 2 == 0 || size < 0) {
|
||||
throw new IllegalArgumentException("La taille doit être impaire et positive");
|
||||
public static void gaussienFilter(Planar<GrayU8> input, int size, double sigma, BorderType borderType) {
|
||||
if (size % 2 == 0 || size < 0 || sigma <= 0) {
|
||||
throw new IllegalArgumentException(
|
||||
"La taille doit être impaire et positive et sigma doit être strictement positif");
|
||||
}
|
||||
double[][] kernel = new double[size][size];
|
||||
double sigma = 4 / 3;
|
||||
double sum = 0.0; // For accumulating the kernel values
|
||||
for (int x = 0; x < size; ++x)
|
||||
for (int y = 0; y < size; ++y) {
|
||||
kernel[x][y] = 1 / (2 * Math.PI * sigma * sigma) * Math.exp(-(x * x + y * y) / (2 * sigma * sigma));
|
||||
|
||||
int half = size / 2;
|
||||
double sum = 0;
|
||||
for (int x = -half; x <= half; ++x) {
|
||||
for (int y = -half; y <= half; ++y) {
|
||||
double gaussianValue = 1 / (2 * Math.PI * sigma * sigma)
|
||||
* Math.exp(-(x * x + y * y) / (2 * sigma * sigma));
|
||||
kernel[x + half][y + half] = gaussianValue;
|
||||
sum += gaussianValue;
|
||||
}
|
||||
}
|
||||
// On récupère le pourcentage de chaque valeur par rapport à la somme
|
||||
for (int x = -half; x <= half; ++x) {
|
||||
for (int y = -half; y <= half; ++y) {
|
||||
kernel[x + half][y + half] = kernel[x + half][y + half] / sum;
|
||||
}
|
||||
}
|
||||
|
||||
// Normalize the kernel
|
||||
int[][] kernelk = new int[size][size];
|
||||
for (int x = 0; x < size; ++x)
|
||||
for (int y = 0; y < size; ++y)
|
||||
kernelk[x][y] = (int) (kernel[x][y] / sum);
|
||||
|
||||
return kernelk;
|
||||
int[][] kernelInt = new int[size][size];
|
||||
double factor = 1 / kernel[0][0];
|
||||
// Si le facteur est trop grand (la valeur du coin est très très petite par
|
||||
// rapport au reste), on le fixe à 1 million
|
||||
if (factor > 1000000) {
|
||||
factor = 1000000;
|
||||
}
|
||||
// normalisation
|
||||
for (int x = 0; x < size; ++x) {
|
||||
for (int y = 0; y < size; ++y) {
|
||||
kernelInt[x][y] = (int) (kernel[x][y] * factor);
|
||||
}
|
||||
}
|
||||
convolution(input, kernelInt, borderType);
|
||||
}
|
||||
|
||||
public static int[][] createGaussienKernel(int size, double sigma) {
|
||||
@@ -398,14 +407,13 @@ public class ImageProcessing {
|
||||
* donné et stocke le résultat dans l'image de sortie.
|
||||
*
|
||||
* @param input L'image d'entrée
|
||||
* @param output L'image de sortie
|
||||
* @param kernel Le noyau de convolution à utiliser.
|
||||
* @param borderType Le type de bord à utiliser pour la convolution
|
||||
*/
|
||||
public static void gaussienFilter(Planar<GrayU8> input, Planar<GrayU8> output,
|
||||
int size) {
|
||||
int[][] kernel = { { 1, 2, 3, 2, 1 }, { 2, 6, 8, 6, 2 }, { 3, 8, 10, 8, 3 }, { 2, 6, 8, 6, 2 },
|
||||
{ 1, 2, 3, 2, 1 } };
|
||||
int half = size / 2;
|
||||
public static void convolution(Planar<GrayU8> input, int[][] kernel, BorderType borderType) {
|
||||
Planar<GrayU8> copy = input.clone();
|
||||
int size = kernel.length;
|
||||
int half = kernel.length / 2;
|
||||
int coefs = 0;
|
||||
// Calcul du nombre total de coefficients dans le noyau
|
||||
for (int i = 0; i < kernel.length; i++) {
|
||||
@@ -417,10 +425,54 @@ public class ImageProcessing {
|
||||
// Parcours de l'image d'entrée et application de la convolution
|
||||
BoofConcurrency.loopFor(0, input.height, y -> {
|
||||
for (int x = 0; x < input.width; x++) {
|
||||
// Vérification si le carré sera à l'intérieur de l'image
|
||||
// On traite d'abord les bords
|
||||
if (y < half || x < half || y >= input.height - half || x >= input.width -
|
||||
half) {
|
||||
setRGBValue(output, x, y, getRGBValue(input, x, y));
|
||||
if (borderType == BorderType.SKIP) {
|
||||
setRGBValue(input, x, y, getRGBValue(copy, x, y));
|
||||
} else if (borderType == BorderType.ZERO) {
|
||||
setRGBValue(input, x, y, new int[] { 0, 0, 0 });
|
||||
} else {
|
||||
int totalValue[] = new int[3];
|
||||
int coefsUsed = 0;
|
||||
for (int j = y - half; j < y + half + 1; j++) {
|
||||
int tmpj = j;
|
||||
if (j < 0 || j >= input.height) {
|
||||
if (borderType == BorderType.EXTENDED) {
|
||||
tmpj = j < 0 ? 0 : input.height - 1;
|
||||
} else if (borderType == BorderType.NORMALIZED) {
|
||||
continue;
|
||||
} else if (borderType == BorderType.REFLECT) {
|
||||
tmpj = j < 0 ? -j : (input.height - 1) - (j - (input.height - 1));
|
||||
} else if (borderType == BorderType.WRAP) {
|
||||
tmpj = j < 0 ? j + (input.height - 1) : (j - (input.height - 1));
|
||||
}
|
||||
}
|
||||
for (int i = x - size / 2; i < x + size / 2 + 1; i++) {
|
||||
int tmpi = i;
|
||||
if (i < 0 || i >= input.width) {
|
||||
if (borderType == BorderType.EXTENDED) {
|
||||
tmpi = i < 0 ? 0 : input.width - 1;
|
||||
} else if (borderType == BorderType.NORMALIZED) {
|
||||
continue;
|
||||
} else if (borderType == BorderType.REFLECT) {
|
||||
tmpi = i < 0 ? -i : (input.width - 1) - (i - (input.width - 1));
|
||||
} else if (borderType == BorderType.WRAP) {
|
||||
tmpi = i < 0 ? i + (input.width - 1) : (i - (input.width - 1));
|
||||
}
|
||||
}
|
||||
for (int k = 0; k < totalValue.length; k++) {
|
||||
totalValue[k] += copy.getBand(k).get(tmpi, tmpj)
|
||||
* kernel[i + (-x + half)][j + (-y + half)];
|
||||
}
|
||||
coefsUsed += kernel[i + (-x + half)][j + (-y + half)];
|
||||
}
|
||||
}
|
||||
for (int k = 0; k < totalValue.length; k++) {
|
||||
totalValue[k] /= coefsUsed;
|
||||
}
|
||||
setRGBValue(input, x, y, totalValue);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
int currentJ = 0;
|
||||
@@ -430,7 +482,7 @@ public class ImageProcessing {
|
||||
for (int j = y - half; j <= y + half; j++) {
|
||||
for (int i = x - half; i <= x + half; i++) {
|
||||
for (int k = 0; k < totalValue.length; k++) {
|
||||
totalValue[k] += input.getBand(k).get(i, j) * kernel[currentJ][currentI];
|
||||
totalValue[k] += copy.getBand(k).get(i, j) * kernel[currentJ][currentI];
|
||||
}
|
||||
currentI++;
|
||||
}
|
||||
@@ -440,7 +492,7 @@ public class ImageProcessing {
|
||||
for (int k = 0; k < totalValue.length; k++) {
|
||||
totalValue[k] /= totalCoefs;
|
||||
}
|
||||
setRGBValue(output, x, y, totalValue);
|
||||
setRGBValue(input, x, y, totalValue);
|
||||
}
|
||||
});
|
||||
}
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package pdl.backend;
|
||||
package pdl.backend.Image;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
package pdl.backend;
|
||||
|
||||
public class Parameter {
|
||||
private final String name;
|
||||
private final String displayName;
|
||||
private final int min;
|
||||
private final int max;
|
||||
private final int step;
|
||||
|
||||
public Parameter(String name, String displayName, int min, int max, int step) {
|
||||
this.name = name;
|
||||
this.displayName = displayName;
|
||||
this.max = max;
|
||||
this.min = min;
|
||||
this.step = step;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public String getDisplayName() {
|
||||
return this.displayName;
|
||||
}
|
||||
|
||||
public int getMin() {
|
||||
return this.min;
|
||||
}
|
||||
|
||||
public int getMax() {
|
||||
return this.max;
|
||||
}
|
||||
|
||||
public int getStep() {
|
||||
return this.step;
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,9 @@ import org.springframework.test.util.ReflectionTestUtils;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
|
||||
|
||||
import pdl.backend.Image.Image;
|
||||
import pdl.backend.Image.ImageDao;
|
||||
|
||||
@SpringBootTest
|
||||
@AutoConfigureMockMvc
|
||||
@TestMethodOrder(OrderAnnotation.class)
|
||||
|
||||
@@ -7,6 +7,8 @@ import java.io.IOException;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import pdl.backend.Image.ImageDao;
|
||||
|
||||
public class ImageDaoTests {
|
||||
|
||||
@Test
|
||||
|
||||
+3
-44
@@ -3,6 +3,7 @@ import Home from './components/Home.vue'
|
||||
import Gallery from './components/Gallery.vue';
|
||||
import NotFound from './components/NotFound.vue'
|
||||
import Upload from './components/Upload.vue'
|
||||
import StableDiff from './components/StableDiff.vue';
|
||||
import { defineComponent } from 'vue'
|
||||
import { api } from '@/http-api'
|
||||
import { ImageType, ImageClass } from './image';
|
||||
@@ -48,6 +49,7 @@ export default defineComponent({
|
||||
<li><router-link :to="{ name: 'home' }">Home</router-link></li>
|
||||
<li><router-link :to="{ name: 'gallery' }">Gallery</router-link></li>
|
||||
<li><router-link :to="{ name: 'upload' }">Upload</router-link></li>
|
||||
<li><router-link :to="{ name: 'generate' }">Generate Images</router-link></li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
@@ -55,12 +57,7 @@ export default defineComponent({
|
||||
<RouterView :images="images" @delete="deleteFile" @updateImageList="updateImageList()" />
|
||||
</template>
|
||||
<style scoped>
|
||||
.navi {
|
||||
/* reduce size of children */
|
||||
display: flex;
|
||||
flex-direction: row-reverse;
|
||||
pointer-events: none;
|
||||
}
|
||||
@import url("@/navi.css");
|
||||
|
||||
header {
|
||||
display: block;
|
||||
@@ -72,17 +69,6 @@ header {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
ul>li+li {
|
||||
margin-left: 30px;
|
||||
}
|
||||
|
||||
ul>li {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.router-link-active {
|
||||
background-color: #646cff;
|
||||
}
|
||||
@@ -90,31 +76,4 @@ ul>li {
|
||||
.router-link-active>a {
|
||||
color: rgb(205, 201, 194);
|
||||
}
|
||||
|
||||
a {
|
||||
padding: 5px 15px;
|
||||
border-radius: 60px;
|
||||
color: rgb(172, 165, 154);
|
||||
text-decoration: none;
|
||||
font-family: "Aeonik", sans-serif;
|
||||
}
|
||||
|
||||
nav {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding-left: 20px;
|
||||
height: 60px;
|
||||
padding-right: 20px;
|
||||
border-radius: 60px;
|
||||
background-color: rgb(24, 26, 27);
|
||||
margin-right: 20px;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
|
||||
ul {
|
||||
display: flex;
|
||||
list-style: none;
|
||||
padding-inline-start: 0;
|
||||
}
|
||||
</style>
|
||||
@@ -1,3 +1,47 @@
|
||||
<template>
|
||||
<div class="custom-select" @blur="open = false">
|
||||
<div class="selected" :class="{ open: open }" @click="open = !open">
|
||||
<span>{{ list[selected] }}</span>
|
||||
</div>
|
||||
<div class="items" :class="{ selectHide: !open }">
|
||||
<div v-for="(name, index) in list" @click="open = false; $emit('updateSelected', index)">
|
||||
<span>{{ name }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
</script>
|
||||
<script lang="ts">
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
list: {
|
||||
type: Array as () => string[],
|
||||
required: true,
|
||||
},
|
||||
selected: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
emits: {
|
||||
updateSelected(id: number) {
|
||||
return id >= 0;
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
open: false,
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.custom-select {
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
@@ -69,3 +113,4 @@
|
||||
.selectHide {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
@@ -19,7 +19,7 @@ export default defineComponent({
|
||||
<div class="flex" v-if="images.size > 0">
|
||||
<div v-for="[id, image] in images" :key="id" class="image">
|
||||
<label>{{ image.name }}</label>
|
||||
<Image class="gallery" :id="id" :images="images" redirect="home" />
|
||||
<Image :id="id" :images="images" redirect="home" />
|
||||
</div>
|
||||
</div>
|
||||
<h1 v-else>Aucune image trouvée</h1>
|
||||
@@ -59,9 +59,8 @@ label {
|
||||
font-size: 1.5em;
|
||||
color: white;
|
||||
}
|
||||
</style>
|
||||
<style>
|
||||
.gallery.imageContainer {
|
||||
|
||||
:deep(.imageContainer) {
|
||||
margin: 2vw;
|
||||
max-height: min(33vh, 33vw);
|
||||
max-width: min(33vw, 33vh);
|
||||
|
||||
@@ -22,7 +22,6 @@ export default defineComponent({
|
||||
data() {
|
||||
return {
|
||||
imageSelectedId: -1,
|
||||
timer: null as ReturnType<typeof setTimeout> | null,
|
||||
editor: false,
|
||||
filter: undefined as Filter | undefined,
|
||||
}
|
||||
@@ -38,6 +37,9 @@ export default defineComponent({
|
||||
'images.size': {
|
||||
handler() { this.updateImageSelectedId(); },
|
||||
},
|
||||
imageSelectedId() {
|
||||
this.filter = undefined;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
updateImageSelectedId(id: number = -1) {
|
||||
@@ -47,35 +49,29 @@ export default defineComponent({
|
||||
this.imageSelectedId = id;
|
||||
else
|
||||
this.imageSelectedId = this.images.keys().next().value;
|
||||
},
|
||||
startTimer() {
|
||||
if (this.timer !== null)
|
||||
clearTimeout(this.timer);
|
||||
this.timer = setTimeout(() => {
|
||||
this.editor = !this.editor;
|
||||
}, 1900);
|
||||
},
|
||||
clearTimer() {
|
||||
if (this.timer !== null) {
|
||||
clearTimeout(this.timer);
|
||||
this.timer = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
<template>
|
||||
<div class="flex" v-if="images.size > 0">
|
||||
<h1 v-if="!editor">MODE SÉLECTION</h1>
|
||||
<h1 v-else>MODE ÉDITEUR</h1>
|
||||
<div class="navi">
|
||||
<nav>
|
||||
<ul>
|
||||
<li>
|
||||
<h3 :class="{ active: !editor }" @click="editor = false">Mode sélection</h3>
|
||||
</li>
|
||||
<li>
|
||||
<h3 :class="{ active: editor }" @click="editor = true">Mode éditeur</h3>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
<HomeSelect v-if="!editor" :images="images" :image-selected-id="imageSelectedId" @delete="id => $emit('delete', id)"
|
||||
@updateImageSelectedId="id => updateImageSelectedId(id)"></HomeSelect>
|
||||
<HomeEditor v-else @apply-filter="(newFilter) => filter = newFilter"></HomeEditor>
|
||||
<h5 v-if="!editor">Restez sur l'image pour passer en mode éditeur</h5>
|
||||
<h5 v-else>Restez sur l'image pour passer en mode selection</h5>
|
||||
<Image @mouseleave="clearTimer()" @mouseenter="startTimer()" class="home"
|
||||
v-if="imageSelectedId !== undefined && imageSelectedId !== -1" :id="imageSelectedId" :images="images"
|
||||
:filter="filter">
|
||||
<Image class="home" v-if="imageSelectedId !== undefined && imageSelectedId !== -1" :id="imageSelectedId"
|
||||
:images="images" :filter="filter">
|
||||
</Image>
|
||||
<a class="button" :href="images.get(imageSelectedId)?.data"
|
||||
:download="images.get(imageSelectedId)?.name">Télécharger {{ images.get(imageSelectedId)?.name }}</a>
|
||||
@@ -83,16 +79,13 @@ export default defineComponent({
|
||||
<h1 v-else>Aucune image trouvée</h1>
|
||||
</template>
|
||||
<style scoped>
|
||||
@import url("@/navi.css");
|
||||
|
||||
h1 {
|
||||
color: white;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
h5 {
|
||||
color: white;
|
||||
margin: 1vw 0 0 0;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
@@ -106,37 +99,24 @@ html {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
<style>
|
||||
.home.imageContainer {
|
||||
max-width: 60%;
|
||||
width: auto;
|
||||
height: auto;
|
||||
max-height: 60vh;
|
||||
margin: 0 0 2vw 0;
|
||||
gap: 4vh;
|
||||
}
|
||||
|
||||
div.home.imageContainer:hover {
|
||||
animation: zoom-in-zoom-out 2s cubic-bezier(1, 0, 0.51, 1.01);
|
||||
.active {
|
||||
background-color: #646cff;
|
||||
}
|
||||
|
||||
.active a {
|
||||
color: rgb(205, 201, 194);
|
||||
}
|
||||
|
||||
.navi h3 {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
:deep(.imageContainer) {
|
||||
max-width: min(60vh, 60vw);
|
||||
max-height: min(60vh, 60vw);
|
||||
transform: none;
|
||||
}
|
||||
|
||||
@keyframes zoom-in-zoom-out {
|
||||
0% {
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
60% {
|
||||
transform: scale(1.5);
|
||||
}
|
||||
|
||||
75% {
|
||||
transform: scale(10);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,8 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
import { defineComponent } from 'vue'
|
||||
import { Filter, clone } from '@/filter'
|
||||
import { Filter, clone, FilterType, RangeParameters, SelectParameters } from '@/filter'
|
||||
import { api } from "@/http-api";
|
||||
import CustomSelector from './CustomSelector.vue';
|
||||
|
||||
</script>
|
||||
<script lang="ts">
|
||||
@@ -15,73 +16,120 @@ export default defineComponent({
|
||||
data() {
|
||||
return {
|
||||
filterSelectId: 0,
|
||||
open: false,
|
||||
filters: [] as Filter[],
|
||||
error: false,
|
||||
}
|
||||
error: "",
|
||||
filterApplied: false,
|
||||
};
|
||||
},
|
||||
created() {
|
||||
api.getAlgorithmList().then((filters) => {
|
||||
this.filters = filters;
|
||||
// fill value attribute
|
||||
for (const filter of this.filters) {
|
||||
for (const arg of filter.parameters) {
|
||||
if (arg.type === FilterType.range) {
|
||||
const range = arg as RangeParameters;
|
||||
range.value = range.min;
|
||||
}
|
||||
else if (arg.type === FilterType.select) {
|
||||
const select = arg as SelectParameters;
|
||||
select.value = select.options[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
this.error = this.areInputValid();
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
applyFilter() {
|
||||
if (this.filterSelectId === 0) {
|
||||
this.$emit("applyFilter", undefined);
|
||||
if (this.error !== "")
|
||||
return;
|
||||
}
|
||||
const filter = clone(this.filters[this.filterSelectId - 1]);
|
||||
const filter = clone(this.filters[this.filterSelectId]);
|
||||
for (const arg of filter.parameters) {
|
||||
if (arg.value === undefined) {
|
||||
this.error = true;
|
||||
this.error = "";
|
||||
return;
|
||||
}
|
||||
}
|
||||
this.filterApplied = true;
|
||||
this.$emit("applyFilter", filter);
|
||||
},
|
||||
areInputValid() {
|
||||
for (const p of this.filters[this.filterSelectId].parameters) {
|
||||
const input = document.getElementById(p.name) as HTMLInputElement;
|
||||
if (input === null)
|
||||
return "";
|
||||
if (!input.checkValidity())
|
||||
return "Paramètre(s) du filtre invalide(s)";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
filterSelectId() {
|
||||
this.error = this.areInputValid();
|
||||
},
|
||||
filters: {
|
||||
handler() {
|
||||
this.error = this.areInputValid();
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
components: { CustomSelector }
|
||||
})
|
||||
|
||||
</script>
|
||||
<template>
|
||||
<div class="editor">
|
||||
<label>Sélectionner un filtre: </label>
|
||||
<div class="custom-select" @blur="open = false">
|
||||
<div class="selected" :class="{ open: open }" @click="open = !open">
|
||||
<span>{{ filterSelectId === 0 ? "Aucun filtre" : filters[filterSelectId - 1].name }}</span>
|
||||
<CustomSelector :list="filters.map((f) => { return f.name })" :selected="filterSelectId"
|
||||
@update-selected="filterSelectId = $event" />
|
||||
<!-- Filter parameters -->
|
||||
<div v-if="filters.length !== 0" v-for="parameter in filters[filterSelectId].parameters" class="rangeInput">
|
||||
<div v-if="parameter.type === FilterType.range">
|
||||
<div class="labelInput">
|
||||
<label>{{ parameter.name }}: </label>
|
||||
<input type="number" :min="(parameter as RangeParameters).min" :max="(parameter as RangeParameters).max"
|
||||
:step="(parameter as RangeParameters).step" v-model.number="parameter.value" :id="parameter.name" />
|
||||
</div>
|
||||
<div class="items" :class="{ selectHide: !open }">
|
||||
<div @click="open = false; filterSelectId = 0">
|
||||
<span>Aucun filtre</span>
|
||||
<input type="range" :min="(parameter as RangeParameters).min" :max="(parameter as RangeParameters).max"
|
||||
:step="(parameter as RangeParameters).step" v-model.number="(parameter as RangeParameters).value" />
|
||||
</div>
|
||||
<div v-for="(filter, index) in filters" @click="open = false; filterSelectId = index + 1">
|
||||
<span>{{ filter.name }}</span>
|
||||
<div v-else-if="parameter.type === FilterType.select" class="selectParam">
|
||||
<label>{{ parameter.name }}: </label>
|
||||
<CustomSelector :list="(parameter as SelectParameters).options"
|
||||
:selected="(parameter as SelectParameters).options.findIndex((o) => o === (parameter as SelectParameters).value)"
|
||||
@update-selected="parameter.value = (parameter as SelectParameters).options[$event]" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="filters.length != 0 && filterSelectId !== 0" v-for="parameter in filters[filterSelectId - 1].parameters"
|
||||
class="rangeInput">
|
||||
<label>{{ parameter.name }}: ({{ parameter.value }}) </label>
|
||||
<input type="range" :min="parameter.min" :max="parameter.max" :step="parameter.step"
|
||||
v-model.number="parameter.value" />
|
||||
</div>
|
||||
<button @mouseleave="error = false" class="button" :class="{ erreurClass: error }" @click="applyFilter()">
|
||||
{{ error ? "Erreur" : "Appliquer le filtre" }}</button>
|
||||
<button @mouseenter="error = areInputValid()" @mouseleave="error = areInputValid()" class="button"
|
||||
:class="{ errorClass: error !== '' }" @click="applyFilter()">
|
||||
{{ error !== "" ? error : "Appliquer le filtre" }}</button>
|
||||
<button v-if="filterApplied" class="button"
|
||||
@click="$emit('applyFilter', undefined); filterApplied = false">Supprimer le filtre</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
@import url("@/customSelect.css");
|
||||
|
||||
label {
|
||||
color: white;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.labelInput {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.labelInput input {
|
||||
width: min-content;
|
||||
height: 1em;
|
||||
font-size: 0.8em;
|
||||
}
|
||||
|
||||
.editor {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
@@ -90,8 +138,19 @@ label {
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.erreurClass {
|
||||
.errorClass {
|
||||
background-color: red;
|
||||
color: white;
|
||||
}
|
||||
|
||||
input:invalid {
|
||||
background-color: red;
|
||||
}
|
||||
|
||||
.selectParam {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
</style>
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { defineComponent } from 'vue'
|
||||
import { ImageType } from '@/image'
|
||||
|
||||
import CustomSelector from './CustomSelector.vue';
|
||||
</script>
|
||||
<script lang="ts">
|
||||
|
||||
@@ -28,7 +28,6 @@ export default defineComponent({
|
||||
data() {
|
||||
return {
|
||||
sure: false,
|
||||
open: false,
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
@@ -52,25 +51,14 @@ export default defineComponent({
|
||||
<template>
|
||||
<div class="selector">
|
||||
<label>Sélectionner une image: </label>
|
||||
<div class="custom-select" @blur="open = false">
|
||||
<div class="selected" :class="{ open: open }" @click="open = !open">
|
||||
<span>{{ images.get(imageSelectedId)?.name }}</span>
|
||||
</div>
|
||||
<div class="items" :class="{ selectHide: !open }">
|
||||
<div v-for="[id, image] of images" :key="id"
|
||||
@click="$emit('updateImageSelectedId', image.id); open = false;">
|
||||
<span>{{ image.name }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<CustomSelector :list="Array.from(images.values()).map((i) => { return i.name })" :selected="imageSelectedId"
|
||||
@update-selected="$emit('updateImageSelectedId', $event)" />
|
||||
<button class="button" @mouseleave="sure = false" @click="confirmDelete()" :class="{ confirm: sure }">
|
||||
{{ sure ? "Confirmer" : "Supprimer" }}</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
@import url(@/customSelect.css);
|
||||
|
||||
.selector {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
@@ -73,9 +73,7 @@ export default defineComponent({
|
||||
<style>
|
||||
.imageContainer img {
|
||||
max-width: 100%;
|
||||
max-height: 100vh;
|
||||
height: auto;
|
||||
width: auto;
|
||||
max-height: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,248 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="preview">
|
||||
<div v-if="started" class="loadingbar">
|
||||
<div class="loadedbar" :style="{ width: progress + '%' }">
|
||||
<span>ETA: {{ eta }}s</span>
|
||||
</div>
|
||||
</div>
|
||||
<img :src="generatedImage" />
|
||||
</div>
|
||||
<div class="dl" v-if="generatedImage != ''">
|
||||
<a :href="generatedImage" download="generated.png" class="button">Download</a>
|
||||
<button class="button" @click="sendImage()" :class="{ sent: sent }">{{ sent ? "Envoyé" : "Upload to server"
|
||||
}}</button>
|
||||
</div>
|
||||
<div class="form">
|
||||
<label for="promptImage">Prompt Image</label>
|
||||
<textarea v-model="promptImage" />
|
||||
<label for="negativePromptImage">Negative Prompt Image</label>
|
||||
<textarea v-model="negativePromptImage" />
|
||||
<div class="labelInput">
|
||||
<label for="height">Height</label>
|
||||
<input type="number" v-model.number="height" />
|
||||
</div>
|
||||
<input type="range" :min="minmaxHeight.min" :max="minmaxHeight.max" v-model.number="height" />
|
||||
<div class="labelInput">
|
||||
<label for="width">Width</label>
|
||||
<input type="number" v-model.number="width" />
|
||||
</div>
|
||||
<input type="range" :min="minmaxWidth.min" :max="minmaxWidth.max" v-model.number="width" />
|
||||
<div class="labelInput">
|
||||
<label for="steps">Steps</label>
|
||||
<input type="number" v-model.number="steps" />
|
||||
</div>
|
||||
<input type="range" :min="minmaxSteps.min" :max="minmaxSteps.max" v-model.number="steps" />
|
||||
<div class="labelInput">
|
||||
<label for="cfgScale">Cfg Scale</label>
|
||||
<input type="number" v-model.number="cfgScale" />
|
||||
</div>
|
||||
<input type="range" :min="minmaxCfgScale.min" :max="minmaxCfgScale.max" v-model.number="cfgScale" />
|
||||
<button :disabled="started" class="button" @click="generate">{{ started ? "Generation en cours" :
|
||||
"Generate" }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import { api } from '@/http-api';
|
||||
</script>
|
||||
<script lang="ts">
|
||||
|
||||
const minmaxHeight = {
|
||||
min: 400,
|
||||
max: 512
|
||||
}
|
||||
|
||||
const minmaxWidth = {
|
||||
min: 400,
|
||||
max: 512
|
||||
}
|
||||
|
||||
const minmaxSteps = {
|
||||
min: 10,
|
||||
max: 40
|
||||
}
|
||||
|
||||
const minmaxCfgScale = {
|
||||
min: 0,
|
||||
max: 30
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
emits: ['updateImageList'],
|
||||
data() {
|
||||
return {
|
||||
promptImage: '',
|
||||
negativePromptImage: '',
|
||||
height: 512,
|
||||
width: 512,
|
||||
steps: 20,
|
||||
cfgScale: 7,
|
||||
generatedImage: '',
|
||||
started: false,
|
||||
progress: 0,
|
||||
eta: 0,
|
||||
sent: false,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async generate() {
|
||||
api.generateImage(this.promptImage, this.negativePromptImage, this.height, this.width, this.steps, this.cfgScale).then((data) => {
|
||||
this.generatedImage = "data:image/png;base64," + data.images[0];
|
||||
this.started = false;
|
||||
});
|
||||
this.sent = false;
|
||||
this.generatedImage = '';
|
||||
this.started = true;
|
||||
},
|
||||
urltoFile(url: string, filename: string, mimeType: string) {
|
||||
return (fetch(url)
|
||||
.then(function (res) { return res.arrayBuffer(); })
|
||||
.then(function (buf) { return new File([buf], filename, { type: mimeType }); })
|
||||
);
|
||||
},
|
||||
async sendImage() {
|
||||
if (!this.sent) {
|
||||
this.sent = true;
|
||||
const file = await this.urltoFile(this.generatedImage, "generated" + Date.now() + ".png", "image/png");
|
||||
const formData = new FormData();
|
||||
formData.append("file", file);
|
||||
api.createImage(formData).then((data) => {
|
||||
console.log(data);
|
||||
this.$emit('updateImageList');
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
height() {
|
||||
if (this.height < minmaxHeight.min)
|
||||
this.height = minmaxHeight.min;
|
||||
else if (this.height > minmaxHeight.max)
|
||||
this.height = minmaxHeight.max;
|
||||
},
|
||||
width() {
|
||||
if (this.width < minmaxWidth.min)
|
||||
this.width = minmaxWidth.min;
|
||||
else if (this.width > minmaxWidth.max)
|
||||
this.width = minmaxWidth.max;
|
||||
},
|
||||
steps() {
|
||||
if (this.steps < minmaxSteps.min)
|
||||
this.steps = minmaxSteps.min;
|
||||
else if (this.steps > minmaxSteps.max)
|
||||
this.steps = minmaxSteps.max;
|
||||
},
|
||||
cfgScale() {
|
||||
if (this.cfgScale < minmaxCfgScale.min)
|
||||
this.cfgScale = minmaxCfgScale.min;
|
||||
else if (this.cfgScale > minmaxCfgScale.max)
|
||||
this.cfgScale = minmaxCfgScale.max;
|
||||
},
|
||||
started() {
|
||||
if (this.started) {
|
||||
const interval = setInterval(() => {
|
||||
api.progress().then((data) => {
|
||||
this.progress = data.progress * 100;
|
||||
this.eta = Math.floor(data.eta_relative);
|
||||
if (data.current_image !== null) {
|
||||
this.generatedImage = "data:image/png;base64," + data.current_image;
|
||||
}
|
||||
if (data.state.job_count === 0) {
|
||||
clearInterval(interval);
|
||||
}
|
||||
if (!this.started) {
|
||||
clearInterval(interval);
|
||||
}
|
||||
});
|
||||
}, 1000);
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
})
|
||||
</script>
|
||||
<style scoped>
|
||||
.form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
label {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.sent {
|
||||
color: green;
|
||||
}
|
||||
|
||||
.form textarea {
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
margin-bottom: 1vh;
|
||||
}
|
||||
|
||||
.form input {
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
margin-bottom: 1vh;
|
||||
}
|
||||
|
||||
a.button {
|
||||
text-decoration: none;
|
||||
height: 1.5em;
|
||||
width: min-content;
|
||||
}
|
||||
|
||||
.dl {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 10px;
|
||||
gap: 1vw;
|
||||
}
|
||||
|
||||
.labelInput {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.labelInput input {
|
||||
width: 37px;
|
||||
}
|
||||
|
||||
.loadingbar {
|
||||
width: 100%;
|
||||
height: 25px;
|
||||
background-color: #c9c9c9;
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
|
||||
.loadedbar {
|
||||
height: 100%;
|
||||
background-color: #4caf50;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: row;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
|
||||
.preview {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.preview img {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
@@ -13,7 +13,7 @@ interface InputFileEvent extends Event {
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
emits: ['updateImageList', 'delete'],
|
||||
emits: ['updateImageList'],
|
||||
data() {
|
||||
return {
|
||||
file: null as FileList | null,
|
||||
@@ -27,8 +27,15 @@ export default defineComponent({
|
||||
images: {
|
||||
type: Map<number, ImageType>,
|
||||
required: true
|
||||
},
|
||||
fileDefault: {
|
||||
type: FileList,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.file = this.fileDefault;
|
||||
},
|
||||
methods: {
|
||||
async submitImage() {
|
||||
if (this.classSendButton === "sent") {
|
||||
|
||||
+57
-12
@@ -1,22 +1,48 @@
|
||||
export class Parameters {
|
||||
export enum FilterType {
|
||||
"range" = "range",
|
||||
"select" = "select",
|
||||
}
|
||||
|
||||
|
||||
export abstract class Parameters {
|
||||
name: string
|
||||
displayName: string
|
||||
min: number
|
||||
max: number
|
||||
step: number
|
||||
value: number | undefined
|
||||
type: FilterType
|
||||
value: any
|
||||
|
||||
constructor(name: string, displayName: string, min: number, max: number, step: number = 1, value: number | undefined = undefined) {
|
||||
constructor(name: string, displayName: string, type: FilterType) {
|
||||
this.name = name
|
||||
this.displayName = displayName
|
||||
this.min = min
|
||||
this.max = max
|
||||
this.step = step
|
||||
this.value = value
|
||||
this.type = type
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class RangeParameters extends Parameters {
|
||||
min: number
|
||||
max: number
|
||||
step: number
|
||||
value: number
|
||||
constructor(name: string, displayName: string, min: number, max: number, step: number, value: number = min) {
|
||||
super(name, displayName, FilterType.range)
|
||||
this.value = value;
|
||||
this.min = min
|
||||
this.max = max
|
||||
this.step = step
|
||||
}
|
||||
}
|
||||
|
||||
export class SelectParameters extends Parameters {
|
||||
options: string[]
|
||||
value: string
|
||||
constructor(name: string, displayName: string, options: string[], value = options[0]) {
|
||||
super(name, displayName, FilterType.select)
|
||||
this.value = value;
|
||||
this.options = options
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class Filter {
|
||||
name: string
|
||||
path: string
|
||||
@@ -31,9 +57,28 @@ export class Filter {
|
||||
}
|
||||
|
||||
export function getParameters(f: Filter): string {
|
||||
return '&' + f.parameters.map((param) => param.name + "=" + param.value).join("&");
|
||||
return '&' + f.parameters.map((param) => {
|
||||
switch (param.type) {
|
||||
case FilterType.range:
|
||||
const paramCastRange = param as RangeParameters
|
||||
return paramCastRange.name + "=" + paramCastRange.value
|
||||
case FilterType.select:
|
||||
const paramCastSelect = param as SelectParameters
|
||||
return paramCastSelect.name + "=" + paramCastSelect.value
|
||||
}
|
||||
}
|
||||
).join("&")
|
||||
}
|
||||
|
||||
export function clone(f: Filter): Filter {
|
||||
return new Filter(f.name, f.path, f.parameters.map((arg) => new Parameters(arg.name, arg.displayName, arg.min, arg.max, arg.step, arg.value)))
|
||||
return new Filter(f.name, f.path, f.parameters.map((param) => {
|
||||
switch (param.type) {
|
||||
case FilterType.range:
|
||||
const paramCastRange = param as RangeParameters
|
||||
return new RangeParameters(param.name, param.displayName, paramCastRange.min, paramCastRange.max, paramCastRange.step, paramCastRange.value)
|
||||
case FilterType.select:
|
||||
const paramCastSelect = param as SelectParameters
|
||||
return new SelectParameters(param.name, param.displayName, paramCastSelect.options, paramCastSelect.value)
|
||||
}
|
||||
}))
|
||||
}
|
||||
@@ -2,9 +2,44 @@ import axios, { AxiosResponse, AxiosError, HttpStatusCode } from 'axios';
|
||||
import { ImageType } from '@/image';
|
||||
import { Filter, getParameters } from './filter';
|
||||
|
||||
interface stateInterface {
|
||||
skipped: boolean;
|
||||
interrupted: boolean;
|
||||
job: string;
|
||||
job_count: number;
|
||||
job_timestamp: string;
|
||||
job_no: number;
|
||||
sampling_step: number;
|
||||
sampling_steps: number;
|
||||
}
|
||||
|
||||
interface progressInterface {
|
||||
progress: number;
|
||||
eta_relative: number;
|
||||
state: stateInterface;
|
||||
current_image: string | null;
|
||||
text_info: string | null;
|
||||
}
|
||||
|
||||
interface generateImageInterface {
|
||||
images: string[];
|
||||
parameters: {};
|
||||
info: string;
|
||||
}
|
||||
|
||||
|
||||
export const cancelRequest = axios.CancelToken.source();
|
||||
|
||||
const instance = axios.create({
|
||||
baseURL: "/",
|
||||
timeout: 15000,
|
||||
timeout: 0,
|
||||
cancelToken: cancelRequest.token
|
||||
});
|
||||
|
||||
const stableDiffInstance = axios.create({
|
||||
baseURL: "http://stable.nessar.fr/",
|
||||
timeout: 0,
|
||||
cancelToken: cancelRequest.token
|
||||
});
|
||||
|
||||
const responseBody = (response: AxiosResponse) => response.data;
|
||||
@@ -14,7 +49,12 @@ const requests = {
|
||||
get: (url: string, param: {}) => instance.get(url, param).then(responseBody),
|
||||
post: (url: string, body: {}) => instance.post(url, body, { headers: { "Content-Type": "multipart/form-data" }, }).then(responseStatus),
|
||||
put: (url: string, body: {}) => instance.put(url, body).then(responseBody),
|
||||
delete: (url: string) => instance.delete(url).then(responseBody)
|
||||
delete: (url: string) => instance.delete(url).then(responseBody),
|
||||
};
|
||||
|
||||
const stableDiffRequests = {
|
||||
get: (url: string, param: {}) => stableDiffInstance.get(url, param).then(responseBody),
|
||||
post: (url: string, body: {}) => stableDiffInstance.post(url, body, { headers: { "Content-Type": "application/json" }, }).then(responseBody),
|
||||
};
|
||||
|
||||
export const api = {
|
||||
@@ -23,4 +63,18 @@ export const api = {
|
||||
getImage: (id: number, filter: Filter | undefined): Promise<Blob> => requests.get(`images/${id}${filter !== undefined ? "?algorithm=" + filter.path + getParameters(filter) : ""}`, { responseType: "blob" }),
|
||||
createImage: (form: FormData): Promise<HttpStatusCode> => requests.post('images', form),
|
||||
deleteImage: (id: number): Promise<void> => requests.delete(`images/${id}`),
|
||||
generateImage: (prompt: string, negativePrompt: string, height: number, width: number, steps: number, cfgScale: number): Promise<generateImageInterface> => stableDiffRequests.post('sdapi/v1/txt2img', {
|
||||
"prompt": prompt,
|
||||
"negative_prompt": negativePrompt,
|
||||
"height": height,
|
||||
"width": width,
|
||||
"steps": steps,
|
||||
"cfg_scale": cfgScale,
|
||||
"do_not_save_samples": true,
|
||||
"do_not_save_grid": true,
|
||||
"sampler_index": "Euler a",
|
||||
"send_images": true,
|
||||
"save_images": false,
|
||||
}),
|
||||
progress: (): Promise<progressInterface> => stableDiffRequests.get('sdapi/v1/progress', {}),
|
||||
};
|
||||
@@ -0,0 +1,43 @@
|
||||
.navi {
|
||||
/* reduce size of children */
|
||||
display: flex;
|
||||
flex-direction: row-reverse;
|
||||
pointer-events: none;
|
||||
gap: 3vw;
|
||||
}
|
||||
|
||||
.navi ul>li {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.navi h3,
|
||||
.navi a {
|
||||
min-width: 90px;
|
||||
padding: 5px 15px;
|
||||
border-radius: 60px;
|
||||
color: rgb(172, 165, 154);
|
||||
text-decoration: none;
|
||||
font-family: "Aeonik", sans-serif;
|
||||
}
|
||||
|
||||
.navi nav {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding-left: 20px;
|
||||
height: 60px;
|
||||
padding-right: 20px;
|
||||
border-radius: 60px;
|
||||
background-color: rgb(24, 26, 27);
|
||||
margin-right: 20px;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
|
||||
.navi ul {
|
||||
display: flex;
|
||||
list-style: none;
|
||||
padding-inline-start: 0;
|
||||
}
|
||||
@@ -18,6 +18,11 @@ const router = createRouter({
|
||||
name: "upload",
|
||||
component: () => import('../components/Upload.vue')
|
||||
},
|
||||
{
|
||||
path: '/generateImages',
|
||||
name: "generate",
|
||||
component: () => import('../components/StableDiff.vue')
|
||||
},
|
||||
{
|
||||
path: '/:pathMatch(.*)*',
|
||||
name: 'not-found',
|
||||
|
||||
Reference in new issue
Block a user