dir rename
parent
42f21a881b
commit
8c67fd0206
@ -0,0 +1,180 @@
|
||||
package mosaic;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.HashMap;
|
||||
|
||||
import mosaic.file.FileHandler;
|
||||
import mosaic.file.SettingsHandler;
|
||||
import mosaic.log.Log;
|
||||
import mosaic.log.LogLevel;
|
||||
import mosaic.picture.ImageAnalyzer;
|
||||
import mosaic.picture.ImageUtils;
|
||||
|
||||
public class Mosaic extends Thread{
|
||||
|
||||
public static final String programmName = "Mosaic",
|
||||
versionString = "Alpha-0.40";
|
||||
|
||||
private static String outputName = "Output";
|
||||
private static int gridWidth = 100, gridHeight = 100, targetMulti = 2,
|
||||
alphaThreshhold = 30,
|
||||
adaptionCount = 300,
|
||||
inputWorkerLimit = 10,
|
||||
targetWorkerLimit = 8,
|
||||
matchWorkerLimit = 10,
|
||||
placeWorkerLimit = 2; //get overwritten by file settings
|
||||
private static double adaptionStep = 1.1, gridErrorThresh = 0.15;
|
||||
private static boolean keepRatio = true, overlapImages = true;
|
||||
/*
|
||||
*
|
||||
* TO DO:
|
||||
* Test 1*1 grid settings
|
||||
*
|
||||
* Write down stats what image was used (how often)
|
||||
* Make settings save & load from a settings file
|
||||
*
|
||||
* Performance:
|
||||
*
|
||||
* FIX:
|
||||
* rasterization doesn't cover everything!
|
||||
* alphaThreshhold is currently dead
|
||||
*
|
||||
* Feature:
|
||||
* explore keeping Input Image Ratio's
|
||||
* explore guarantee of usage of at least once, each image.
|
||||
*/
|
||||
|
||||
public SettingsHandler sh;
|
||||
public FileHandler fh;
|
||||
public ImageAnalyzer ia;
|
||||
|
||||
public Mosaic(){
|
||||
fh = new FileHandler("resources");
|
||||
Log.log(LogLevel.Info, "Starting "+programmName+" "+versionString);
|
||||
ia = new ImageAnalyzer(fh, inputWorkerLimit, targetWorkerLimit, matchWorkerLimit,
|
||||
placeWorkerLimit, alphaThreshhold, keepRatio, overlapImages);
|
||||
sh = new SettingsHandler(fh, "settings.txt");
|
||||
this.start();
|
||||
}
|
||||
|
||||
public void run(){
|
||||
Log.log.perfLog("Started "+programmName+" v."+versionString);
|
||||
|
||||
Log.log(LogLevel.Info, "Checking for settings file ...");
|
||||
if(sh.settingsFile.exists()){
|
||||
Log.log(LogLevel.Info, "Importing settings from file ...");
|
||||
importSettings(sh.loadSettings());
|
||||
}else{
|
||||
Log.log(LogLevel.Info, "Couldn't find a settings file. Dumping hard-coded settings ...");
|
||||
sh.saveSettings(exportSettings());
|
||||
}
|
||||
System.out.println("gridHeightxxx:"+gridHeight);
|
||||
|
||||
Log.log.perfLog("Starting indexing ...");
|
||||
prepMatching();
|
||||
|
||||
while(ia.isPrepInProgress()){
|
||||
try {
|
||||
Mosaic.sleep(500);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
Log.log.perfLog("Finished Index!");
|
||||
Log.log.perfLog("Calculating Matches and reloading Index ...");
|
||||
prepAssembly();
|
||||
while(ia.isMatchInProgress()){
|
||||
try {
|
||||
Mosaic.sleep(500);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
Log.log.perfLog("Finished Matching!");
|
||||
Log.log.perfLog("Starting placement of images ...");
|
||||
createMosaic();
|
||||
while(ia.isPlaceInProgress()){
|
||||
try {
|
||||
Mosaic.sleep(500);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
ImageUtils.cutOutGrid(ia);
|
||||
Log.log.perfLog("Finished Placement!");
|
||||
Log.log(LogLevel.Info, "Finished placement. Output is done ...");
|
||||
Log.log.perfLog("Saving output to file ...");
|
||||
fh.saveImage(ia.canvas, new File(fh.OutputFolder+fh.fs+outputName+"-"+fh.OutputFolder.listFiles().length+".png"));
|
||||
Log.log.perfLog("Everything done! Exiting ...");
|
||||
Log.log.finishPerfLog();
|
||||
Log.shutdownLog();
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares the assembly of the Mosaic.
|
||||
*
|
||||
* Starts threads to index all not indexed images and rasterizes and classifies the Target.
|
||||
*/
|
||||
public void prepMatching(){
|
||||
ia.rasterizeTarget(gridWidth, gridHeight, targetMulti, gridErrorThresh, adaptionCount, adaptionStep);
|
||||
ia.updateIndex();
|
||||
ia.classifyTarget();
|
||||
}
|
||||
|
||||
public void prepAssembly(){
|
||||
ia.reloadIndex();
|
||||
ia.calculateMatches();
|
||||
}
|
||||
|
||||
public void createMosaic(){
|
||||
Log.log(LogLevel.Info, "Starting Creation of Mosaic !");
|
||||
ia.placeFragments();
|
||||
}
|
||||
|
||||
public void importSettings(HashMap<String, String> settings){
|
||||
if(0 < settings.get("version").compareTo(versionString)){
|
||||
Log.log(LogLevel.Error, "Won't import settings! Version String doesn't match!");
|
||||
Log.log(LogLevel.Error, "Read: "+versionString+" Expected: "+settings.get("version"));
|
||||
return;
|
||||
}
|
||||
outputName = settings.get("outputName");
|
||||
gridWidth = Integer.parseInt(settings.get("gridWidth"));
|
||||
gridHeight = Integer.parseInt(settings.get("gridHeight"));
|
||||
targetMulti = Integer.parseInt(settings.get("targetMultiplier"));
|
||||
alphaThreshhold = Integer.parseInt(settings.get("alphaThreshhold"));
|
||||
adaptionCount = Integer.parseInt(settings.get("adaptionCount"));
|
||||
inputWorkerLimit = Integer.parseInt(settings.get("inputWorkerLimit"));
|
||||
targetWorkerLimit = Integer.parseInt(settings.get("targetWorkerLimit"));
|
||||
matchWorkerLimit = Integer.parseInt(settings.get("matchWorkerLimit"));
|
||||
placeWorkerLimit = Integer.parseInt(settings.get("placeWorkerLimit"));
|
||||
adaptionStep = Double.parseDouble(settings.get("adaptionStep"));
|
||||
gridErrorThresh = Double.parseDouble(settings.get("gridErrorThresh"));
|
||||
keepRatio = Boolean.parseBoolean(settings.get("keepRatio"));
|
||||
overlapImages = Boolean.parseBoolean(settings.get("overlapImages"));
|
||||
}
|
||||
|
||||
public HashMap<String, String> exportSettings(){
|
||||
HashMap<String, String> settings = new HashMap<String, String>();
|
||||
settings.put("version", versionString);
|
||||
settings.put("outputName", outputName);
|
||||
settings.put("gridWidth", Integer.toString(gridWidth));
|
||||
settings.put("gridHeight", Integer.toString(gridHeight));
|
||||
settings.put("targetMultiplier", Integer.toString(targetMulti));
|
||||
settings.put("alphaThreshhold", Integer.toString(alphaThreshhold));
|
||||
settings.put("adaptionCount", Integer.toString(adaptionCount));
|
||||
settings.put("inputWorkerLimit", Integer.toString(inputWorkerLimit));
|
||||
settings.put("targetWorkerLimit", Integer.toString(targetWorkerLimit));
|
||||
settings.put("matchWorkerLimit", Integer.toString(matchWorkerLimit));
|
||||
settings.put("placeWorkerLimit", Integer.toString(placeWorkerLimit));
|
||||
settings.put("adaptionStep", Double.toString(adaptionStep));
|
||||
settings.put("gridErrorThresh", Double.toString(gridErrorThresh));
|
||||
settings.put("keepRatio", Boolean.toString(keepRatio));
|
||||
settings.put("overlapImages", Boolean.toString(overlapImages));
|
||||
|
||||
return settings;
|
||||
}
|
||||
|
||||
public static void main(String[] args){
|
||||
new Mosaic();
|
||||
}
|
||||
}
|
@ -0,0 +1,304 @@
|
||||
package mosaic.file;
|
||||
|
||||
import java.awt.Dimension;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileReader;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Scanner;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
|
||||
import mosaic.log.Log;
|
||||
import mosaic.log.LogLevel;
|
||||
|
||||
public class FileHandler {
|
||||
|
||||
public File sourceFolder, InputImagesFolder, TargetImageFile, OutputFolder, indexFile;
|
||||
/*
|
||||
* sourcePath/ -> all ressources
|
||||
* sourcePath/Images/ -> Input pictures folder
|
||||
* sourcePath/Target -> Target picture file
|
||||
* sourcePath/Output/ -> Output picture folder
|
||||
*/
|
||||
public final String fs;
|
||||
|
||||
public FileHandler(String sourcePath){
|
||||
if(!System.getProperty("os.name").startsWith("Windows")){
|
||||
fs = "/";
|
||||
}else{
|
||||
fs = "\\";
|
||||
}
|
||||
this.sourceFolder = new File(sourcePath);
|
||||
this.InputImagesFolder = new File(sourceFolder.getAbsolutePath()+fs+"Images");
|
||||
this.TargetImageFile = new File(sourceFolder.getAbsolutePath()+fs+"Target");
|
||||
this.OutputFolder = new File(sourceFolder.getAbsolutePath()+fs+"Output");
|
||||
this.indexFile = new File(this.InputImagesFolder.getAbsolutePath()+"Index.txt");
|
||||
if(!this.sourceFolder.exists()){
|
||||
this.sourceFolder.mkdirs();
|
||||
}
|
||||
Log.initLog(this.sourceFolder.getAbsolutePath(), this);
|
||||
if(fs == "\\"){
|
||||
Log.log(LogLevel.Debug, "Assumed Windows like Folder declaration. Therefore using "+fs+" as a separator.");
|
||||
}else{
|
||||
Log.log(LogLevel.Debug, "Detected Linux or OSX.");
|
||||
}
|
||||
if(!this.validateFolderStructure()){
|
||||
Log.log(LogLevel.Error, "Could not validate folder structure! Things are missing!");
|
||||
Log.spawnReadMe(this);
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean validateFolderStructure(){
|
||||
if(this.sourceFolder.isDirectory()){
|
||||
Log.log(LogLevel.Debug, "Detected source folder at "+this.sourceFolder.getAbsolutePath());
|
||||
if(this.InputImagesFolder.isDirectory()){
|
||||
Log.log(LogLevel.Debug, "Detected Input folder at "+this.InputImagesFolder.getAbsolutePath());
|
||||
if(this.OutputFolder.isDirectory()){
|
||||
Log.log(LogLevel.Debug, "Detected Output folder at "+this.OutputFolder.getAbsolutePath());
|
||||
}else{
|
||||
Log.log(LogLevel.Info, "No Output folder found.");
|
||||
Log.log(LogLevel.Info, "Creating one at "+this.OutputFolder.getAbsolutePath());
|
||||
this.OutputFolder.mkdirs();
|
||||
}
|
||||
if(this.TargetImageFile.isFile()){
|
||||
Log.log(LogLevel.Debug, "Detected Target Image at "+this.TargetImageFile.getAbsolutePath());
|
||||
if(!this.indexFile.isDirectory()){
|
||||
Log.log(LogLevel.Debug, "Found no directory blocking the index file.");
|
||||
return true;
|
||||
}
|
||||
else{
|
||||
Log.log(LogLevel.Error, "Following folder collides with the index file name: "+this.indexFile.getAbsolutePath());
|
||||
return false;
|
||||
}
|
||||
}else{
|
||||
Log.log(LogLevel.Critical, "No Target Image found! Exiting...");
|
||||
return false;
|
||||
}
|
||||
}else{
|
||||
Log.log(LogLevel.Critical, "No Input folder found.");
|
||||
Log.log(LogLevel.Critical, "Creating one at "+this.InputImagesFolder.getAbsolutePath());
|
||||
this.InputImagesFolder.mkdirs();
|
||||
}
|
||||
}else{
|
||||
Log.log(LogLevel.Critical, "No source folder found (redundant check).");
|
||||
Log.log(LogLevel.Critical, "Creating one at "+this.sourceFolder.getAbsolutePath());
|
||||
this.sourceFolder.mkdirs();
|
||||
}
|
||||
Log.log(LogLevel.Critical, "Folder validation failed. There could be a permission problem "
|
||||
+ "or a folder needed to be created! Please look for earlier errors.");
|
||||
return false;
|
||||
}
|
||||
|
||||
public ArrayList<String> listInputFiles(){
|
||||
Log.log(LogLevel.Info, "Listing files inside "+this.InputImagesFolder.getName()+" ...");
|
||||
ArrayList<String> fileList = new ArrayList<String>();
|
||||
for(File f: this.InputImagesFolder.listFiles()){
|
||||
if(f.isFile()){
|
||||
fileList.add(f.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
return fileList;
|
||||
}
|
||||
|
||||
public BufferedImage loadImage(File file){
|
||||
Log.log(LogLevel.Info, "Loading image "+file.getName()+" ...");
|
||||
if(file.isFile() && file.canRead()){
|
||||
BufferedImage img;
|
||||
try {
|
||||
img = ImageIO.read(file);
|
||||
Log.log(LogLevel.Debug, "Loaded image "+file.getName()+" !");
|
||||
return img;
|
||||
} catch (IOException e) {
|
||||
Log.log(LogLevel.Debug, "File "+file.getPath()+" failed to load as an Image. What did I just read?");
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
else{
|
||||
Log.log(LogLevel.Info, "Can't read file "+file.getPath()+" ! It could be a directory or no read permissions.");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public void saveImage(BufferedImage img, File file){
|
||||
Log.log(LogLevel.Info, "Saving image as file "+file.getAbsolutePath());
|
||||
Log.log(LogLevel.Info, "This could take a moment ...");
|
||||
try {
|
||||
ImageIO.write(img, "png", file);
|
||||
} catch (IOException e) {
|
||||
Log.log(LogLevel.Critical, "Couldn't write image "+file.getName()+" to file! Are write permissions missing?"
|
||||
+ " Attempted to write at "+file.getAbsolutePath());
|
||||
e.printStackTrace();
|
||||
}
|
||||
Log.log(LogLevel.Info, "Saved "+file.getName()+" !");
|
||||
}
|
||||
|
||||
/**
|
||||
* Plainly appends the given file and rgb value to the end of the index.
|
||||
*
|
||||
* CHECK FOR DUPLICATES BEFOREHAND.
|
||||
* @param file
|
||||
* @param rgb
|
||||
*/
|
||||
public synchronized void appendToIndex(File file, int rgb){
|
||||
try{
|
||||
BufferedWriter bw = new BufferedWriter(new FileWriter(indexFile, true));
|
||||
bw.write(rgb+";"+file.getName()+"\n");
|
||||
bw.flush();
|
||||
bw.close();
|
||||
Log.log(LogLevel.Info, "Wrote index entry for "+file.getName()+" !");
|
||||
} catch (IOException e) {
|
||||
Log.log(LogLevel.Critical, "Couldn't create or write index file "+indexFile.getAbsolutePath()+" ."
|
||||
+ "Are write permissions missing?");
|
||||
e.printStackTrace();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads an index entry for a single file.
|
||||
*
|
||||
* Loads and parses the whole index in the background if not given the indexData.
|
||||
* @param indexData
|
||||
* @param file
|
||||
* @return
|
||||
*/
|
||||
public Integer loadIndexEntry(HashMap<String, Integer> indexData, File file){
|
||||
Log.log(LogLevel.Debug, "Searching for index data of "+file.getName()+" ...");
|
||||
if(indexData == null){
|
||||
indexData = loadIndex();
|
||||
}
|
||||
return indexData.get(file.getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the whole index file into a Hashmap.
|
||||
* The second value (file name) is used as a String-key, the rgb value is the int-value.
|
||||
* @param file
|
||||
* @return
|
||||
*/
|
||||
public HashMap<String, Integer> loadIndex(){
|
||||
if(!this.indexFile.exists()){
|
||||
Log.log(LogLevel.Info, "No Index file found. Nothing to load then. Creating empty one ...");
|
||||
try {
|
||||
indexFile.createNewFile();
|
||||
} catch (IOException e) {
|
||||
Log.log(LogLevel.Error, "Couldn't create Index file! Are write permissions missing?");
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
HashMap<String, Integer> indexData = new HashMap<String, Integer>();
|
||||
try {
|
||||
Scanner sc = new Scanner(new BufferedReader(new FileReader(this.indexFile)));
|
||||
while(sc.hasNext()){
|
||||
@SuppressWarnings("rawtypes")
|
||||
ArrayList data = parseIndexData(sc.nextLine());
|
||||
indexData.put((String)data.get(1), (int)data.get(0));
|
||||
}
|
||||
sc.close();
|
||||
} catch (FileNotFoundException e) {
|
||||
Log.log(LogLevel.Critical, "Could not open index file! Do I have read permissions?");
|
||||
e.printStackTrace();
|
||||
}
|
||||
Log.log(LogLevel.Debug, "Sucessfully loaded index!");
|
||||
return indexData;
|
||||
}
|
||||
|
||||
public void overwriteIndex(HashMap<String, Integer> index){
|
||||
try{
|
||||
BufferedWriter bw = new BufferedWriter(new FileWriter(indexFile, false));
|
||||
for(String key: index.keySet()){
|
||||
bw.write(index.get(key)+";"+key+"\n");
|
||||
}
|
||||
bw.flush();
|
||||
bw.close();
|
||||
Log.log(LogLevel.Info, "Overwrote index file with new index!");
|
||||
} catch (IOException e) {
|
||||
Log.log(LogLevel.Critical, "Couldn't create or write index file "+indexFile.getAbsolutePath()+" ."
|
||||
+ "Are write permissions missing?");
|
||||
e.printStackTrace();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a line of indexData into an ArrayList containing the rgb int and the name
|
||||
* @param indexData
|
||||
* @return
|
||||
*/
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
private ArrayList parseIndexData(String indexData){
|
||||
String[] data = indexData.split(";");
|
||||
ArrayList result = new ArrayList();
|
||||
result.add(Integer.parseInt(data[0]));
|
||||
result.add(data[1]);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// could use a single image variant to check synchronized to the classification (saves IO)
|
||||
public Dimension loadBiggestDimension(){
|
||||
File[] files = this.InputImagesFolder.listFiles();
|
||||
int width = 0, height = 0, img_count = 0;
|
||||
for(File f: files){
|
||||
if(f.isFile() && f.canRead()){
|
||||
BufferedImage img = loadImage(f);
|
||||
if(img == null){
|
||||
continue;
|
||||
}
|
||||
img_count++;
|
||||
if(width < img.getWidth()){
|
||||
width = img.getWidth();
|
||||
}
|
||||
if(height < img.getHeight()){
|
||||
height = img.getHeight();
|
||||
}
|
||||
}
|
||||
else{
|
||||
Log.log(LogLevel.Info, "Can't read file"+f.toString()+"! It could be a directory or no read permissions.");
|
||||
}
|
||||
}
|
||||
Log.log(LogLevel.Info, img_count+" image(s) were loaded...");
|
||||
if(width == 0 || height == 0){
|
||||
Log.log(LogLevel.Critical, "Incomplete or no dimension values! Could I load any Image?");
|
||||
}
|
||||
Log.log(LogLevel.Debug, "Biggest dimension is "+width+"x"+height);
|
||||
return new Dimension(width, height);
|
||||
}
|
||||
|
||||
// Would probably kill memory (and performance).
|
||||
@Deprecated
|
||||
public BufferedImage[] loadAllImages(){
|
||||
File[] files = this.InputImagesFolder.listFiles();
|
||||
ArrayList<BufferedImage> imgs = new ArrayList<BufferedImage>();
|
||||
int img_count = 0;
|
||||
for(File f: files){
|
||||
BufferedImage img = loadImage(f);
|
||||
if(img == null){
|
||||
continue;
|
||||
}
|
||||
imgs.add(img);
|
||||
img_count++;
|
||||
}
|
||||
if(imgs.size() == 0){
|
||||
Log.log(LogLevel.Critical, "No Images found in "+this.InputImagesFolder.getAbsolutePath());
|
||||
return null;
|
||||
}
|
||||
Log.log(LogLevel.Info, img_count+" image(s) were loaded...");
|
||||
BufferedImage[] bfs = new BufferedImage[imgs.size()];
|
||||
for(int i = 0; i < imgs.size(); i++){
|
||||
bfs[i] = imgs.get(i);
|
||||
}
|
||||
return bfs;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,70 @@
|
||||
package mosaic.file;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileReader;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Scanner;
|
||||
|
||||
import mosaic.log.Log;
|
||||
import mosaic.log.LogLevel;
|
||||
|
||||
public class SettingsHandler {
|
||||
|
||||
public File settingsFile;
|
||||
|
||||
public SettingsHandler(FileHandler fh, String settings){
|
||||
this.settingsFile = new File(fh.sourceFolder.getAbsolutePath()+fh.fs+settings);;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the settings to file.
|
||||
* @param settings
|
||||
*/
|
||||
public void saveSettings(HashMap<String, String> settings){
|
||||
try {
|
||||
Log.log(LogLevel.Info, "Saving settings to file ...");
|
||||
BufferedWriter bw = new BufferedWriter(new FileWriter(settingsFile, false));
|
||||
for(String key: settings.keySet()){
|
||||
bw.write(key+"="+settings.get(key)+"\n");
|
||||
bw.flush();
|
||||
}
|
||||
bw.close();
|
||||
} catch (IOException e) {
|
||||
Log.log(LogLevel.Error, "Couldn't write settings file at "+settingsFile.getAbsolutePath()+" ! Are write permissions missing?");
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads all settings from file.
|
||||
* @return
|
||||
*/
|
||||
public HashMap<String, String> loadSettings(){
|
||||
HashMap<String, String> settings;
|
||||
try {
|
||||
settings = new HashMap<String, String>();
|
||||
Scanner sc = new Scanner(new BufferedReader(new FileReader(settingsFile)));
|
||||
while(sc.hasNext()){
|
||||
String raw = sc.nextLine();
|
||||
if(!raw.matches(".*=.*")){
|
||||
continue;
|
||||
}
|
||||
String[] entry = raw.split("=");
|
||||
//System.out.println(entry[0]+":"+entry[1]);
|
||||
settings.put(entry[0], entry[1]);
|
||||
}
|
||||
sc.close();
|
||||
return settings;
|
||||
} catch (FileNotFoundException e) {
|
||||
Log.log(LogLevel.Error, "Couldn't load settings file at "+settingsFile.getAbsolutePath()+" ! Are read permissions missing?");
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,175 @@
|
||||
package mosaic.log;
|
||||
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import mosaic.file.FileHandler;
|
||||
|
||||
public class Log {
|
||||
|
||||
public static Log log;
|
||||
public static final boolean silenceDebug = true,
|
||||
appendEvents = false, appendErrors = false;
|
||||
|
||||
private final static String readmeText =
|
||||
"This is the ReadMe for Mosaic.\n"
|
||||
+ "The folders that were created serve following purpose: \n"
|
||||
+ "resources - Is the main folder where all images (Input AND Output are stored). \n"
|
||||
+ "resources%sImages - Is the Input folder for all images you want to use as \"paint\" in the Mosaic. Put all your images in there!\n"
|
||||
+ "resources%sOutput - Stores all resulting Mosaic pictures. There are all named Output-{number}.png \n"
|
||||
+ "resources%sTarget - Is an Image (or symbolic Link to one) of your choice. Mosaic will try to create the mosaic after this inspiration. The name MUST be \"Target\" without any file extension or it will not be recognized."
|
||||
+ "resources%sERROR.log - Is a log file where all non-fatal erros are stored. Take a peek if problems occur. \n"
|
||||
+ "resources%seventLog.log - Is a log for more genereal events. Like progress, events and such with time stamps. Most useful for debugging problems.";
|
||||
|
||||
private String location;
|
||||
public final File eventFile, errorFile;
|
||||
private File perfFile;
|
||||
private BufferedWriter eventWriter, errorWriter, perfWriter;
|
||||
|
||||
private ArrayList<Long> nanoTimes;
|
||||
|
||||
public Log(String location, FileHandler fh){
|
||||
this.location = location;
|
||||
this.eventFile = new File(location+fh.fs+"eventLog.log");
|
||||
this.errorFile = new File(location+fh.fs+"ERROR.log");
|
||||
this.perfFile = new File(location+fh.fs+"perf.log");
|
||||
this.nanoTimes = new ArrayList<Long>();
|
||||
|
||||
try {
|
||||
if(!this.eventFile.exists()){
|
||||
this.eventFile.createNewFile();
|
||||
}
|
||||
if(!this.errorFile.exists()){
|
||||
this.errorFile.createNewFile();
|
||||
}
|
||||
if(!this.perfFile.exists()){
|
||||
this.perfFile.createNewFile();
|
||||
}
|
||||
this.eventWriter = new BufferedWriter(new FileWriter(eventFile, appendEvents));
|
||||
this.errorWriter = new BufferedWriter(new FileWriter(errorFile, appendErrors));
|
||||
this.perfWriter = new BufferedWriter(new FileWriter(perfFile, true));
|
||||
perfWriter.write("\n\n");
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
public static synchronized void initLog(String location, FileHandler fh){
|
||||
if(Log.log != null){
|
||||
return;
|
||||
}
|
||||
|
||||
Log.log = new Log(location, fh);
|
||||
}
|
||||
|
||||
public static void shutdownLog(){
|
||||
if(Log.log == null){
|
||||
return;
|
||||
}
|
||||
try {
|
||||
Log.log.errorWriter.close();
|
||||
Log.log.eventWriter.close();
|
||||
Log.log.perfWriter.close();
|
||||
Log.log = null;
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public static void log(int logLvl, String message){
|
||||
Log.log(LogLevel.values()[logLvl], message);
|
||||
}
|
||||
|
||||
public static void log(LogLevel lv, String message){
|
||||
if(silenceDebug && LogLevel.Debug == lv){
|
||||
return;
|
||||
}
|
||||
Log.log.logs(lv.ordinal(), message);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public synchronized void logs(int logLvl, String message){
|
||||
String prefix = LogLevel.values()[logLvl].toString();
|
||||
prefix = "["+prefix+"]";
|
||||
BufferedWriter logWriter;
|
||||
if(silenceDebug && logLvl == LogLevel.Debug.ordinal()){
|
||||
return;
|
||||
}
|
||||
|
||||
if(logLvl == LogLevel.Error.ordinal()){
|
||||
logWriter = this.errorWriter;
|
||||
}
|
||||
else{
|
||||
logWriter = this.eventWriter;
|
||||
}
|
||||
String timeStamp = new java.util.Date().toString();
|
||||
String msg = "["+timeStamp+"]"+prefix+" "+message;
|
||||
System.out.println(msg);
|
||||
try {
|
||||
logWriter.write(msg+"\n");
|
||||
if(LogLevel.Info.ordinal() < logLvl){ //Saves perfomance?
|
||||
logWriter.flush();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void perfLog(String message){
|
||||
long currentTime = System.nanoTime();
|
||||
String timeStamp = new java.util.Date().toString()+"|"+currentTime;
|
||||
this.nanoTimes.add(currentTime);
|
||||
try {
|
||||
perfWriter.write(message+"\n");
|
||||
} catch (IOException e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public void finishPerfLog(){
|
||||
String[] stages = {
|
||||
"Indexing & Rasterization", // 2 - 1 1
|
||||
"Matching", // 4 - 3 2
|
||||
"Placement", // 6 - 5 3
|
||||
"Saving" // 8 - 7 4
|
||||
};
|
||||
for(int i = 1; i <= stages.length; i++){
|
||||
long duration = nanoTimes.get(i*2) - nanoTimes.get(i*2-1);
|
||||
try {
|
||||
perfWriter.write(stages[i-1]+": "+duration+"\n");
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
try {
|
||||
perfWriter.flush();
|
||||
perfWriter.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public static void spawnReadMe(FileHandler fh){
|
||||
File readme = new File(Log.log.location+fh.fs+"README.txt");
|
||||
Log.log(LogLevel.Info, "Spawning README file at "+readme.getAbsolutePath());
|
||||
try {
|
||||
BufferedWriter bw = new BufferedWriter(new FileWriter(readme));
|
||||
String rdme = readmeText.replaceAll("%s", fh.fs);
|
||||
bw.write(rdme);
|
||||
bw.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
/*
|
||||
public static void main(String[] args){
|
||||
Log.initLog("/home/mosaic/Software_Projects/EclipseWorkspace/Picture Mosaic/resources/");
|
||||
Log.log(LogLevel.Debug, "Test!");
|
||||
Log.log(LogLevel.Error, "TEST ERROR");
|
||||
}*/
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
package mosaic.log;
|
||||
|
||||
public enum LogLevel {
|
||||
Info,
|
||||
Debug,
|
||||
Critical,
|
||||
Error
|
||||
|
||||
}
|
@ -0,0 +1,416 @@
|
||||
package mosaic.picture;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.awt.image.ColorModel;
|
||||
import java.io.File;
|
||||
import java.text.DecimalFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
|
||||
import mosaic.file.FileHandler;
|
||||
import mosaic.log.Log;
|
||||
import mosaic.log.LogLevel;
|
||||
import mosaic.picture.worker.PlacementWorker;
|
||||
import mosaic.picture.worker.PlainImageAnalyzerWorker;
|
||||
import mosaic.picture.worker.MatchWorker;
|
||||
import mosaic.picture.worker.TargetImageAnalyzerWorker;
|
||||
|
||||
public class ImageAnalyzer {
|
||||
|
||||
public BufferedImage target;
|
||||
public FileHandler fh;
|
||||
|
||||
private Dimension /*biggestInputSize,*/ targetSize;
|
||||
public int preSlotWidth, preSlotHeight, postSlotWidth, postSlotHeight, slotX, slotY, slotCount;
|
||||
public BufferedImage canvas;
|
||||
private HashMap<String, Integer> index;
|
||||
|
||||
private int alphaThreshhold;
|
||||
public final boolean keepRatio, overlapImages;
|
||||
public int[] gridEnd;
|
||||
|
||||
//Input Classification Worker
|
||||
private int inputWorkersLimit;
|
||||
private static final String inputWorkerName = "inputWorker";
|
||||
private PlainImageAnalyzerWorker[] inputWorkers;
|
||||
private ArrayList<File> inputFiles;
|
||||
//
|
||||
|
||||
//Target Classification Worker
|
||||
private int targetWorkersLimit;
|
||||
private static final String targetWorkerName = "targetWorker";
|
||||
private TargetImageAnalyzerWorker[] targetWorkers;
|
||||
public HashMap<String, Integer> slotClassifications; //TODO change back private
|
||||
//
|
||||
|
||||
//Matching Worker
|
||||
private int matchWorkersLimit;
|
||||
private static final String matchWorkerName = "matchWorker";
|
||||
private MatchWorker[] matchWorkers;
|
||||
private HashMap<int[], File[]> fileMap;
|
||||
//
|
||||
|
||||
//Placement Worker
|
||||
private static final String placeWorkerName = "placeWorker";
|
||||
private PlacementWorker placeWorker;
|
||||
//
|
||||
|
||||
public ImageAnalyzer(FileHandler fh, int inputWorkersLimit, int targetWorkersLimit,
|
||||
int matchWorkersLimit, int placeWorkersLimit, int alphaThreshhold, boolean keepRatio,
|
||||
boolean overlapImages){
|
||||
this.fh = fh;
|
||||
this.target = fh.loadImage(fh.TargetImageFile);
|
||||
this.alphaThreshhold = alphaThreshhold;
|
||||
this.keepRatio = keepRatio;
|
||||
this.overlapImages = overlapImages;
|
||||
this.gridEnd = new int[2];
|
||||
|
||||
this.inputWorkersLimit = inputWorkersLimit;
|
||||
this.targetWorkersLimit = targetWorkersLimit;
|
||||
this.matchWorkersLimit = matchWorkersLimit;
|
||||
|
||||
this.slotClassifications = new HashMap<String, Integer>();
|
||||
this.fileMap = new HashMap<int[], File[]>();
|
||||
this.inputFiles = new ArrayList<File>();
|
||||
|
||||
this.inputWorkers = new PlainImageAnalyzerWorker[inputWorkersLimit];
|
||||
this.targetWorkers = new TargetImageAnalyzerWorker[targetWorkersLimit];
|
||||
this.matchWorkers = new MatchWorker[matchWorkersLimit];
|
||||
this.index = fh.loadIndex();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if any of the Worker's are still out there doing stuff.
|
||||
*
|
||||
* @return true if at least one worker of any kind is still alive!
|
||||
*/
|
||||
public boolean isPrepInProgress(){
|
||||
for(PlainImageAnalyzerWorker pWorker: inputWorkers){
|
||||
if(pWorker != null && pWorker.isAlive()){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
for(TargetImageAnalyzerWorker tWorker: targetWorkers){
|
||||
if(tWorker != null && tWorker.isAlive()){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isMatchInProgress(){
|
||||
for(MatchWorker mWorker: matchWorkers){
|
||||
if(mWorker != null && mWorker.isAlive()){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isPlaceInProgress(){
|
||||
if(this.placeWorker != null && this.placeWorker.isAlive()){
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public synchronized void reloadIndex(){
|
||||
Log.log(LogLevel.Debug, "Reloading index!");
|
||||
this.index = fh.loadIndex();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the index by spawning worker threads to classify new input Images.
|
||||
*
|
||||
* Checks for already indexed images before adding them to the workload.
|
||||
* To check if workers are still running check worker.getRunning() or worker.isAlive() (latter preferred)
|
||||
* Worker instances are found in this.inputWorkers
|
||||
*/
|
||||
public void updateIndex(){
|
||||
Log.log(LogLevel.Info, "Starting to update Index.");
|
||||
|
||||
Log.log(LogLevel.Debug, "Filling work list with (unread) input files ...");
|
||||
for(File f: fh.InputImagesFolder.listFiles()){
|
||||
if(this.fh.loadIndexEntry(index, f) == null){
|
||||
Log.log(LogLevel.Debug, "Unindexed input file "+f.getName()+" found! Adding to worklist ...");
|
||||
inputFiles.add(f);
|
||||
}else{
|
||||
Log.log(LogLevel.Debug, "Already indexed input file "+f.getName()+" found!");
|
||||
}
|
||||
}
|
||||
Log.log(LogLevel.Info, "Work list filled with "+inputFiles.size()+" file(s)!");
|
||||
|
||||
if(inputFiles.size() <= 0){
|
||||
Log.log(LogLevel.Info, "No work then! Ending Index Update ...");
|
||||
return;
|
||||
}
|
||||
Log.log(LogLevel.Info, "Spawning input workers ...");
|
||||
inputWorkers = new PlainImageAnalyzerWorker[inputWorkersLimit];
|
||||
int count = 0;
|
||||
for(int i = 0; i < inputWorkersLimit; i++){
|
||||
if(inputFiles.size() <= i){
|
||||
Log.log(LogLevel.Debug, "Allowed more workers than ther was work. Stopped spawning on "+i+" worker(s)!");
|
||||
break;
|
||||
}
|
||||
inputWorkers[i] = new PlainImageAnalyzerWorker(this, inputWorkerName+Integer.toString(i),
|
||||
inputFiles.get(i), this.alphaThreshhold);
|
||||
count++;
|
||||
}
|
||||
Log.log(LogLevel.Info, count+" worker(s) spawned!");
|
||||
}
|
||||
|
||||
/**
|
||||
* Securely retrieves new workload for PlainImageAnalyzerWorker instances.
|
||||
*
|
||||
* Returns null when the list is empty.
|
||||
* @return File for PlainImageAnalyzerWorker to work with.
|
||||
*/
|
||||
public synchronized File getNewInputFile(){
|
||||
if(inputFiles.size() == 0){
|
||||
return null;
|
||||
}
|
||||
File file = inputFiles.get(0);
|
||||
inputFiles.remove(0);
|
||||
return file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts classification of the target image's slots via spawning threaded workers.
|
||||
*
|
||||
* To check if workers are still running check worker.getRunning() or worker.isAlive() (latter preferred)
|
||||
* Worker instances are found in this.targetWorkers
|
||||
*/
|
||||
public void classifyTarget(){
|
||||
Log.log(LogLevel.Info, "Starting Target Classification. Calculating workload and spawning worker(s) ...");
|
||||
this.targetWorkers = new TargetImageAnalyzerWorker[targetWorkersLimit+1];
|
||||
Log.log(LogLevel.Debug, slotCount+" slot(s) need to be classified!");
|
||||
int workload = this.slotCount / (this.targetWorkersLimit);
|
||||
int initialWork = this.slotCount % workload;
|
||||
int currWorker = 0;
|
||||
if(initialWork != 0){
|
||||
this.targetWorkers[0] = new TargetImageAnalyzerWorker(this, this.targetWorkerName+"0", 0, initialWork);
|
||||
currWorker++;
|
||||
}
|
||||
|
||||
int currWork = initialWork;
|
||||
for(int i = currWorker; i < targetWorkersLimit; i++){
|
||||
targetWorkers[i] = new TargetImageAnalyzerWorker(this, this.targetWorkerName+Integer.toString(i), currWork, currWork+workload);
|
||||
currWork += workload;
|
||||
}
|
||||
targetWorkers[targetWorkersLimit] = new TargetImageAnalyzerWorker(this, this.targetWorkerName+Integer.toString(targetWorkersLimit), currWork, currWork+workload);
|
||||
Log.log(LogLevel.Debug, "Ended on assigning "+(currWork+workload)+" slot(s)!");
|
||||
Log.log(LogLevel.Info, "Spawned "+(currWorker+1)+" target worker(s)");
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the given classification map to the classification map of the TargetImage.
|
||||
*
|
||||
* (invoked by target worker instances to deliver finished workloads)
|
||||
* @param clFragment HashMap with classifications and coordinates (slot) as key.
|
||||
*/
|
||||
public synchronized void addSlotClassifications(HashMap<int[], Integer> clFragment, String workerName){
|
||||
for(int[] key: clFragment.keySet()){
|
||||
if(!slotClassifications.containsKey(ImageUtils.parseCoord(key))){
|
||||
this.slotClassifications.put(ImageUtils.parseCoord(key), clFragment.get(key));
|
||||
Log.log(LogLevel.Debug, "Got a classification added by "+workerName+" "+key[0]+" "+key[1]);
|
||||
/*if(key[0] == 199 && key[1] == 0){
|
||||
Log.log(LogLevel.Error, "ImageAnalyzer.addSlotClassifications() - key[0]==30 && key[1]==0");
|
||||
Log.log(LogLevel.Error, "Brrrriiiing!");
|
||||
Log.log(LogLevel.Error, "parseCoord: "+ImageUtils.parseCoord(key));
|
||||
Log.log(LogLevel.Error, "deliveredFrag: "+clFragment.get(key));
|
||||
Log.log(LogLevel.Error, "result: "+this.slotClassifications.get(ImageUtils.parseCoord(key)));
|
||||
Log.log(LogLevel.Error, "");
|
||||
}*/
|
||||
}else{
|
||||
Log.log(LogLevel.Error, "Caused by ["+workerName+"] with "+key[0]+" "+key[1]);
|
||||
Log.log(LogLevel.Error, "Multiple classifcation of target slot detected! Workloads were not sliced correctly or coordinates are screwed up!");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void calculateMatches(){
|
||||
Log.log(LogLevel.Info, "Starting to match slots to indexed Images. Spawning workers ...");
|
||||
this.matchWorkers = new MatchWorker[matchWorkersLimit];
|
||||
int workload = this.slotCount / this.matchWorkersLimit;
|
||||
int initialWork = this.slotCount % workload;
|
||||
int currWorker = 0;
|
||||
int[] lel = {62, 68};
|
||||
if(initialWork != 0){
|
||||
this.matchWorkers[0] = new MatchWorker(this, matchWorkerName+"0", 0, initialWork, 0,
|
||||
slotClassifications, index);
|
||||
currWorker++;
|
||||
}
|
||||
|
||||
int currWork = initialWork;
|
||||
for(int i = currWorker; i < matchWorkersLimit; i++){
|
||||
matchWorkers[i] = new MatchWorker(this, matchWorkerName+"0", currWork, currWork+workload, 0,
|
||||
slotClassifications, index);;
|
||||
currWork += workload;
|
||||
}
|
||||
Log.log(LogLevel.Info, "Spawned "+(currWorker+1)+" match worker(s)");
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges the given fileMapFragment with this instance's one.
|
||||
*
|
||||
* (invoked by MatchWorker instances)
|
||||
* @param fileMapFragment
|
||||
*/
|
||||
public synchronized void addFileMaps(HashMap<int[], File[]> fileMapFragment){
|
||||
for(int[] key: fileMapFragment.keySet()){
|
||||
if(!fileMap.containsKey(key)){
|
||||
this.fileMap.put(key, fileMapFragment.get(key));
|
||||
}else{
|
||||
Log.log(LogLevel.Error, "Multiple fileMap fragments for "+key.toString()+" available! Something went wrong ...");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts processing the fileMap and queues workloads for PlacementWorker instances and spawns them.
|
||||
*/
|
||||
public void placeFragments(){
|
||||
Log.log(LogLevel.Info, "Starting to gather Coordinates for each Image.");
|
||||
HashMap<File, ArrayList<int[]>> coordMap = new HashMap<File, ArrayList<int[]>>();
|
||||
for(int[] coord: fileMap.keySet()){
|
||||
File file = fileMap.get(coord)[0];
|
||||
|
||||
if(!coordMap.containsKey(file)){
|
||||
coordMap.put(file, new ArrayList<int[]>());
|
||||
}
|
||||
coordMap.get(file).add(coord);
|
||||
}
|
||||
Log.log(LogLevel.Info, "Start placing fragments on the target.");
|
||||
this.placeWorker = new PlacementWorker(this, this.placeWorkerName, coordMap);
|
||||
}
|
||||
|
||||
public void rasterizeTarget(int gridX, int gridY, float targetSizeMultiplier,
|
||||
double gridErrorThresh, int adaptionCount, double adaptionStep){
|
||||
Log.log(LogLevel.Info, "Rasterizing target image ...");
|
||||
Log.log(LogLevel.Info, "Aiming for "+gridX+"*"+gridY+"="+(gridX*gridY)+" tiles ...");
|
||||
Log.log(LogLevel.Debug, "Target is "+target.getWidth()+"x"+target.getHeight()+" big!");
|
||||
//this.biggestInputSize = this.fh.loadBiggestDimension();
|
||||
this.targetSize = new Dimension(this.target.getWidth(), this.target.getHeight());
|
||||
|
||||
int count = 0;
|
||||
DecimalFormat df = new DecimalFormat("#");
|
||||
while(true){
|
||||
double resultSlotWidth = ((targetSize.width*targetSizeMultiplier)/gridX);
|
||||
double resultSlotHeight = ((targetSize.height*targetSizeMultiplier)/gridY);
|
||||
postSlotWidth = (int)resultSlotWidth;
|
||||
postSlotHeight = (int)resultSlotHeight;
|
||||
|
||||
Log.log(LogLevel.Debug, "Perfect slot Size would be "+resultSlotWidth+"x"+resultSlotHeight);
|
||||
double widthLoss = Math.abs(resultSlotWidth - Double.parseDouble(df.format(resultSlotWidth)));
|
||||
double heightLoss = Math.abs(resultSlotHeight - Double.parseDouble(df.format(resultSlotHeight)));
|
||||
if(widthLoss > gridErrorThresh || heightLoss > gridErrorThresh){
|
||||
Log.log(LogLevel.Critical, "Grid misplacement error exceeded threshhold! Error is width:"+widthLoss+" height:"+heightLoss);
|
||||
if(widthLoss > gridErrorThresh){
|
||||
gridX *= adaptionStep;
|
||||
}
|
||||
if(heightLoss > gridErrorThresh){
|
||||
gridY *= adaptionStep;
|
||||
}
|
||||
Log.log(LogLevel.Info, "This is the "+(++count)+". adaption to ease the error.");
|
||||
Log.log(LogLevel.Info, "Aiming for "+gridX+"*"+gridY+"="+(gridX*gridY)+" tiles ...");
|
||||
if(count > adaptionCount){
|
||||
Log.log(LogLevel.Critical, "Could not adapt to grid misplacement error! The result might be cut off or missing parts!");
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}else{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
preSlotWidth = (int) ((targetSize.width)/gridX);
|
||||
preSlotHeight = (int) ((targetSize.height)/gridY);
|
||||
//
|
||||
|
||||
|
||||
Log.log(LogLevel.Debug, "Target will be "+(int)(targetSize.width*targetSizeMultiplier)
|
||||
+"x"+(int)(targetSize.height*targetSizeMultiplier)+" big.");
|
||||
Log.log(LogLevel.Debug, "Slots on the Target are "+preSlotWidth+"x"+preSlotHeight+" big.");
|
||||
Log.log(LogLevel.Debug, "Slots on the Output are "+postSlotWidth+"x"+postSlotHeight+" big.");
|
||||
slotX = gridX;
|
||||
slotY = gridY;
|
||||
slotCount = gridX*gridY;
|
||||
//ia.slotWidth*gridX+"x"+ia.slotHeight*gridY
|
||||
//canvas = new BufferedImage((int)(targetSize.getWidth()*targetSizeMultiplier), (int)(targetSize.getHeight()*targetSizeMultiplier), BufferedImage.TYPE_INT_ARGB);
|
||||
canvas = new BufferedImage(postSlotWidth*gridX, postSlotHeight*gridY, BufferedImage.TYPE_INT_ARGB);
|
||||
Graphics2D g2 = (Graphics2D) canvas.getGraphics();
|
||||
g2.setColor(Color.BLACK);
|
||||
g2.fillRect(0, 0, canvas.getWidth(), canvas.getHeight());
|
||||
}
|
||||
|
||||
/**
|
||||
* Places the given input in its specified slot (gridX, gridY) on the canvas.
|
||||
* Assumes and checks boundaries calculated by rasterizeTarget().
|
||||
* @param gridX
|
||||
* @param gridY
|
||||
* @param input
|
||||
* @param canvas
|
||||
* @return
|
||||
*/
|
||||
public synchronized void placeImage(int gridX, int gridY, BufferedImage input, boolean keepRatio){
|
||||
assert(gridX < slotX && gridY < slotY);
|
||||
assert(input.getWidth() < postSlotWidth && input.getHeight() < postSlotHeight);
|
||||
|
||||
int picWidth, picHeight;
|
||||
if(keepRatio){
|
||||
picWidth = input.getWidth();
|
||||
picHeight = input.getHeight();
|
||||
}else{
|
||||
picWidth = postSlotWidth;
|
||||
picHeight = postSlotHeight;
|
||||
}
|
||||
|
||||
Graphics2D g2 = (Graphics2D)canvas.getGraphics();
|
||||
g2.drawImage(input, gridX*postSlotWidth, gridY*postSlotHeight, input.getWidth(), input.getHeight(), null);
|
||||
if(gridEnd[0] < gridX*postSlotWidth+postSlotWidth){
|
||||
gridEnd[0] = gridX*postSlotWidth+postSlotWidth;
|
||||
}
|
||||
if(gridEnd[1] < gridY*postSlotHeight+postSlotHeight){
|
||||
gridEnd[1] = gridY*postSlotHeight+postSlotHeight;
|
||||
}
|
||||
g2.dispose();
|
||||
//Log.log(LogLevel.Error, "Drawn picture at "+gridX*postSlotWidth+" "+gridY*postSlotHeight+" with "+input.getWidth()+"x"+input.getHeight());
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
public static void main(String[] args){
|
||||
System.out.println("ZHE DEBUG");
|
||||
/*
|
||||
FileHandler fh = new FileHandler("/home/mosaic/Software_Projects/EclipseWorkspace/Picture Mosaic/resources");
|
||||
ImageAnalyzer ia = new ImageAnalyzer(fh);
|
||||
|
||||
HashMap<String, Integer> index = fh.loadIndex();
|
||||
for(File f: fh.InputImagesFolder.listFiles()){
|
||||
BufferedImage img = fh.loadImage(f);
|
||||
if(img == null){
|
||||
continue;
|
||||
}
|
||||
int indexValue = ia.classifyImage(img, f, 30);
|
||||
fh.appendToIndex(index, f, indexValue);
|
||||
}
|
||||
index = fh.loadIndex();
|
||||
System.out.println(index.get("mosaic-sketch.png"));
|
||||
System.out.println(index.get("rasd"));
|
||||
System.out.println(index.get("Larry the Tiger - Candy Shop Logo (Gift).png"));
|
||||
System.out.println(ia.matchClosestIndex(index, -10744103, 5));
|
||||
//BufferedImage img = ImageUtils.resizeImage(fh.loadImage(fh.InputImagesFolder.listFiles()[0]), new Dimension(500, 300), false);
|
||||
//fh.saveImage(img, new File("/home/mosaic/Software_Projects/EclipseWorkspace/Picture Mosaic/resources/resize-test.png"));
|
||||
|
||||
}*/
|
||||
|
||||
}
|
@ -0,0 +1,114 @@
|
||||
package mosaic.picture;
|
||||
|
||||
import java.awt.Dimension;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.Image;
|
||||
import java.awt.image.BufferedImage;
|
||||
|
||||
import mosaic.log.Log;
|
||||
import mosaic.log.LogLevel;
|
||||
|
||||
public class ImageUtils {
|
||||
|
||||
private static final int scalingMethod = Image.SCALE_SMOOTH;
|
||||
|
||||
/**
|
||||
* TODO Needs cleanup
|
||||
* @param input
|
||||
* @param targetSize
|
||||
* @param keepRatio
|
||||
* @param overlapImages
|
||||
* @return
|
||||
*/
|
||||
public static BufferedImage resizeImage(BufferedImage input, Dimension targetSize, boolean keepRatio,
|
||||
boolean overlapImages){
|
||||
Image tmp;
|
||||
BufferedImage img;
|
||||
int imageWidth, imageHeight;
|
||||
if(!keepRatio){
|
||||
imageWidth = (int)targetSize.getWidth();
|
||||
imageHeight = (int)targetSize.getHeight();
|
||||
}
|
||||
else{
|
||||
if(!overlapImages){
|
||||
if(input.getWidth() > input.getHeight()){
|
||||
imageWidth = (int)targetSize.getWidth();
|
||||
imageHeight = -1;
|
||||
}else{
|
||||
imageWidth = -1;
|
||||
imageHeight = (int)targetSize.getHeight();
|
||||
}
|
||||
}else{
|
||||
if(input.getWidth() < input.getHeight()){
|
||||
imageWidth = (int)targetSize.getWidth();
|
||||
imageHeight = -1;
|
||||
}else{
|
||||
imageWidth = -1;
|
||||
imageHeight = (int)targetSize.getHeight();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tmp = input.getScaledInstance(imageWidth, imageHeight, scalingMethod);
|
||||
img = new BufferedImage(tmp.getWidth(null), tmp.getHeight(null), BufferedImage.TYPE_INT_ARGB);
|
||||
|
||||
Graphics2D g2 = (Graphics2D) img.getGraphics();
|
||||
g2.drawImage(tmp, 0, 0, null);
|
||||
g2.dispose();
|
||||
return img;
|
||||
}
|
||||
|
||||
public static void cutOutGrid(ImageAnalyzer ia){
|
||||
Log.log(LogLevel.Info, "Cutting out what the grid covered!");
|
||||
Log.log(LogLevel.Debug, "Cutting out 0 0 "+ia.gridEnd[0]+" "+ia.gridEnd[1]);
|
||||
ia.canvas = ia.canvas.getSubimage(0, 0, ia.gridEnd[0], ia.gridEnd[1]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the given number into the slot coordinates (not pixel coordinates).
|
||||
* @param ia
|
||||
* @param num
|
||||
* @return
|
||||
*/
|
||||
public static int[] getSlotCoord(ImageAnalyzer ia, int num){
|
||||
int[] coords = new int[2];
|
||||
coords[0] = num%(ia.slotX+1);
|
||||
coords[1] = num/(ia.slotX+1);
|
||||
return coords;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts from a numbered Slot to a specific slot start coordinate.
|
||||
*
|
||||
* @param num
|
||||
* @param preMagnification true if slot sizes before the magnification are to be used.
|
||||
* @return
|
||||
*/
|
||||
public static int[] getSlotCoordPixels(ImageAnalyzer ia, int[] coords, boolean preMagnification){
|
||||
//TODO BUGGGSSS
|
||||
/*
|
||||
* Error collision values (num1 num2 preSlotDimensions -> result):
|
||||
* 18614 18855 7x3 -> 399x174
|
||||
* 18565 18806 7x3 -> 56x174
|
||||
* 6735 6976 7x3 -> 1596x63
|
||||
* 5833 6074 7x3 -> 343x54
|
||||
* 382 623 7x3 -> 987x3
|
||||
*/
|
||||
int slotWidth, slotHeight;
|
||||
if(preMagnification){
|
||||
slotWidth = ia.preSlotWidth;
|
||||
slotHeight = ia.preSlotHeight;
|
||||
}else{
|
||||
slotWidth = ia.postSlotWidth;
|
||||
slotHeight = ia.postSlotHeight;
|
||||
}
|
||||
|
||||
//TODO -----> FIX überschlag von Zeile 0 in 1; x zählt zu viel!
|
||||
int[] pixelCoords = {coords[0]*slotWidth, coords[1]*slotHeight};
|
||||
return pixelCoords;
|
||||
}
|
||||
|
||||
public static String parseCoord(int[] coord){
|
||||
return coord[0]+"-"+coord[1];
|
||||
}
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
package mosaic.picture.worker;
|
||||
|
||||
import mosaic.picture.ImageAnalyzer;
|
||||
|
||||
public abstract class ImageAnalyzerWorker extends Thread{
|
||||
|
||||
protected ImageAnalyzer ia;
|
||||
protected boolean running;
|
||||
|
||||
public ImageAnalyzerWorker(ImageAnalyzer ia, String name){
|
||||
this.ia = ia;
|
||||
this.setName(name);
|
||||
|
||||
running = true;
|
||||
}
|
||||
|
||||
public abstract void run();
|
||||
|
||||
public boolean getRunning(){
|
||||
return this.running;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,125 @@
|
||||
package mosaic.picture.worker;
|
||||
|
||||
import java.awt.image.ColorModel;
|
||||
import java.io.File;
|
||||
import java.util.HashMap;
|
||||
|
||||
import mosaic.log.Log;
|
||||
import mosaic.log.LogLevel;
|
||||
import mosaic.picture.ImageAnalyzer;
|
||||
import mosaic.picture.ImageUtils;
|
||||
|
||||
public class MatchWorker extends ImageAnalyzerWorker{
|
||||
|
||||
private int startCoord, endCoord, deviation;
|
||||
HashMap<String, Integer> slotClassifications;
|
||||
HashMap<String, Integer> index;
|
||||
|
||||
public MatchWorker(ImageAnalyzer ia, String name, int startCoord, int endCoord, int deviation,
|
||||
HashMap<String, Integer> slotClassifications, HashMap<String, Integer> index){
|
||||
super(ia, name);
|
||||
|
||||
this.deviation = deviation;
|
||||
this.startCoord = startCoord;
|
||||
this.endCoord = endCoord;
|
||||
this.slotClassifications = slotClassifications;
|
||||
this.index = index;
|
||||
this.ia = ia;
|
||||
|
||||
this.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
Log.log(LogLevel.Debug, "["+this.getName()+"] has started its duty!");
|
||||
HashMap<int[], File[]> fileMap = new HashMap<int[], File[]>();
|
||||
for(int i = startCoord; i < endCoord; i++){
|
||||
int[] coord = {i%ia.slotX, i/ia.slotX};
|
||||
if(coord[0]+ia.preSlotWidth >= ia.target.getWidth() || coord[1]+ia.preSlotHeight >= ia.target.getHeight()){
|
||||
//Dirty FIX
|
||||
//This will inevitably land outside the Raster otherwise. I should prevent these Coords from the start
|
||||
//Log.log(LogLevel.Error, "Didn't classify "+coord[0]+" "+coord[1]);
|
||||
continue;
|
||||
}
|
||||
if(coord[0] == ia.slotX-1 || coord[1] == ia.slotY-1){
|
||||
//Dirty Fix
|
||||
//for avoiding non-existant keys in the slotClassifications.
|
||||
continue;
|
||||
}
|
||||
if(index == null || slotClassifications.get(ImageUtils.parseCoord(coord)) == null){ //TODO remove
|
||||
Log.log(LogLevel.Error, "MatchWorker run() -> slotClassifications.get(ImageUtils.parseCoord(coord)==null");
|
||||
Log.log(LogLevel.Error, "BRrrring"+slotClassifications.get(ImageUtils.parseCoord(coord)));
|
||||
Log.log(LogLevel.Error, "parsed: "+ImageUtils.parseCoord(coord));
|
||||
Log.log(LogLevel.Error, "Unparsed: "+coord[0]+" "+coord[1]);
|
||||
Log.log(LogLevel.Error, "");
|
||||
for(String key: slotClassifications.keySet()){
|
||||
//Log.log(LogLevel.Error, key);
|
||||
}
|
||||
}
|
||||
HashMap<String, Integer> matches = matchClosestIndex(index, slotClassifications.get(ImageUtils.parseCoord(coord)), deviation);
|
||||
Log.log(LogLevel.Debug, "["+this.getName()+"] Matched "+matches.keySet().size()+" image(s) to slot "+i+" !");
|
||||
File[] files = new File[matches.keySet().size()];
|
||||
for(int n = 0; n < files.length; n++){
|
||||
files[n] = new File(ia.fh.InputImagesFolder.getAbsolutePath()+ia.fh.fs+(String) matches.keySet().toArray()[n]);
|
||||
}
|
||||
|
||||
fileMap.put(coord, files);
|
||||
}
|
||||
ia.addFileMaps(fileMap);
|
||||
Log.log(LogLevel.Info, "["+this.getName()+"] This worker has finished its chunk! Terminating ...");
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares the given rgb value with the index and returns a HashMap of the closest hits.
|
||||
* Unlikely to yield more than 1 result without deviationPercentage
|
||||
* @param index
|
||||
* @param rgb
|
||||
* @param deviation How much the matches can be different from the perfect match, and still be taken.
|
||||
* @return
|
||||
*/
|
||||
public HashMap<String, Integer> matchClosestIndex(HashMap<String, Integer> index, int rgb, int deviation){
|
||||
if(index == null){
|
||||
System.out.println("Briiing!");
|
||||
}
|
||||
Log.log(LogLevel.Debug, "Searching for closest match for rgb:"+rgb+" in the index with deviation of "+deviation+".");
|
||||
assert(deviation < 100 && 0 <= deviation);
|
||||
if(index == null){
|
||||
Log.log(LogLevel.Critical, "No Index was given for rgb matching. Exiting!");
|
||||
return null;
|
||||
}
|
||||
HashMap<String, Double> metrics = new HashMap<String, Double>();
|
||||
double currBest = -1;
|
||||
for(String key: index.keySet()){
|
||||
double metric = getMetric(index.get(key), rgb);
|
||||
metrics.put(key, metric);
|
||||
if(currBest > metric || currBest == -1){
|
||||
currBest = metric;
|
||||
}
|
||||
}
|
||||
Log.log(LogLevel.Debug, "Calculated all metrics for rgb:"+rgb+" !");
|
||||
|
||||
HashMap<String, Integer> matches = new HashMap<String, Integer>();
|
||||
for(String key: metrics.keySet()){
|
||||
if(metrics.get(key) == currBest || metrics.get(key) < currBest+(currBest*(deviation/(double)100))){
|
||||
matches.put(key, index.get(key));
|
||||
}
|
||||
}
|
||||
Log.log(LogLevel.Debug, "Grabbed all good matches for rgb:"+rgb+" ! Got "+matches.size()+" ...");
|
||||
return matches;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the euclidian metric of two given rgb values.
|
||||
* @param rgb1
|
||||
* @param rgb2
|
||||
* @return
|
||||
*/
|
||||
private double getMetric(int rgb1, int rgb2){
|
||||
ColorModel cm = ColorModel.getRGBdefault();
|
||||
double result = Math.sqrt(Math.pow((cm.getRed(rgb1)-cm.getRed(rgb2)), 2)+
|
||||
Math.pow(cm.getGreen(rgb1)-cm.getGreen(rgb2), 2)+
|
||||
Math.pow(cm.getBlue(rgb1)-cm.getBlue(rgb2), 2));
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
package mosaic.picture.worker;
|
||||
|
||||
import java.awt.Dimension;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
|
||||
import mosaic.log.Log;
|
||||
import mosaic.log.LogLevel;
|
||||
import mosaic.picture.ImageAnalyzer;
|
||||
import mosaic.picture.ImageUtils;
|
||||
|
||||
public class PlacementWorker extends ImageAnalyzerWorker{
|
||||
|
||||
private HashMap<File, ArrayList<int[]>> coordMap;
|
||||
|
||||
public PlacementWorker(ImageAnalyzer ia, String name, HashMap<File, ArrayList<int[]>> coordMap){
|
||||
super(ia, name);
|
||||
this.coordMap = coordMap;
|
||||
|
||||
this.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
Log.log(LogLevel.Info, "Worker "+this.getName()+" commencing work!");
|
||||
for(File file: coordMap.keySet()){
|
||||
if(coordMap.get(file) == null){ //Check to avoid NullPointerException
|
||||
continue;
|
||||
}
|
||||
BufferedImage img = ia.fh.loadImage(file);
|
||||
ArrayList<int[]> coords = coordMap.get(file);
|
||||
img = ImageUtils.resizeImage(img, new Dimension(ia.postSlotWidth, ia.postSlotHeight), ia.keepRatio, ia.overlapImages);
|
||||
Log.log(LogLevel.Debug, "["+this.getName()+"] Resized image "+file.getName()+" to "+img.getWidth()+"x"+img.getHeight()+" !");
|
||||
|
||||
Log.log(LogLevel.Info, "["+this.getName()+"] Going to place \""+file.getName()+"\" "+coords.size()+" time(s)!");
|
||||
int count = 0;
|
||||
for(int[] coord: coords){
|
||||
ia.placeImage(coord[0], coord[1], img, ia.keepRatio);
|
||||
if(count % 100 == 0){
|
||||
Log.log(LogLevel.Info, "["+this.getName()+"] Placed "+count+"/"+coords.size()+" instances! Continuing ...");
|
||||
}
|
||||
count++;
|
||||
}
|
||||
Log.log(LogLevel.Debug, "["+this.getName()+"] Finished placing all "+file.getName()+" ! Switching file!");
|
||||
}
|
||||
Log.log(LogLevel.Info, "["+this.getName()+"] Finished all work! Terminating ...");
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,90 @@
|
||||
package mosaic.picture.worker;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.awt.image.ColorModel;
|
||||
import java.io.File;
|
||||
|
||||
import mosaic.log.Log;
|
||||
import mosaic.log.LogLevel;
|
||||
import mosaic.picture.ImageAnalyzer;
|
||||
|
||||
public class PlainImageAnalyzerWorker extends ImageAnalyzerWorker{
|
||||
|
||||
private File file;
|
||||
private int alphaThreshhold;
|
||||
|
||||
public PlainImageAnalyzerWorker(ImageAnalyzer ia, String name, File file, int alphaThreshhold){
|
||||
super(ia, name);
|
||||
this.alphaThreshhold = alphaThreshhold;
|
||||
this.file = file;
|
||||
|
||||
this.start();
|
||||
}
|
||||
|
||||
public void run(){
|
||||
Log.log(LogLevel.Info, "Worker "+this.getName()+" up and running! Let's roll...");
|
||||
while(running){
|
||||
System.out.println(file.getAbsolutePath());
|
||||
BufferedImage img = this.ia.fh.loadImage(file);
|
||||
if(img == null){
|
||||
this.file = ia.getNewInputFile();
|
||||
if(this.file == null){
|
||||
running = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
int rgb = this.classifyImage(img, file, alphaThreshhold);
|
||||
ia.fh.appendToIndex(file, rgb);
|
||||
Log.log(LogLevel.Debug, "["+this.getName()+"] finished workunit:"+file.getName()+" !");
|
||||
file = ia.getNewInputFile();
|
||||
while(file != null && !file.exists() ){
|
||||
Log.log(LogLevel.Critical, "Non-existent file eneded up as a task for worker "+this.getName()+" !");
|
||||
file = ia.getNewInputFile();
|
||||
Log.log(LogLevel.Debug, "["+this.getName()+"] got new workunit:"+file.getName()+" !");
|
||||
}
|
||||
if(file == null){
|
||||
Log.log(LogLevel.Debug, "["+this.getName()+"] New workload was null ! Ran out of work ...");
|
||||
running = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
Log.log(LogLevel.Info, "Worker "+this.getName()+" has finished work! Terminating ...");
|
||||
}
|
||||
|
||||
/**
|
||||
* Plain calculation of average Color of an Image via RGB.
|
||||
* Takes average of RGB values of each pixel.
|
||||
* Does not account for alpha.
|
||||
* @param img Image to process
|
||||
* @param alphaThreshhold value under which pixels are discarded
|
||||
* @return RGB value of the average color
|
||||
*/
|
||||
public int classifyImage(BufferedImage img, File file, int alphaTreshhold){
|
||||
int width = img.getWidth();
|
||||
int height = img.getHeight();
|
||||
Log.log(LogLevel.Debug, "Starting classification of "+file.getName()+" with width:"+
|
||||
width+" and height:"+height+" ...");
|
||||
|
||||
ColorModel cm = ColorModel.getRGBdefault();
|
||||
float red = 0, green = 0, blue = 0;
|
||||
int pixels = 0;
|
||||
for(int x = 0; x < img.getWidth(); x++){
|
||||
for(int y = 0; y < img.getHeight(); y++){
|
||||
int rgb = img.getRGB(x, y);
|
||||
red += cm.getRed(rgb);
|
||||
blue += cm.getBlue(rgb);
|
||||
green += cm.getGreen(rgb);
|
||||
pixels++;
|
||||
}
|
||||
}
|
||||
red = red/pixels;
|
||||
green = green/pixels;
|
||||
blue = blue/pixels;
|
||||
int rgb = new Color((int)red, (int)green, (int)blue).getRGB();
|
||||
Log.log(LogLevel.Debug, "Classified "+file.getPath()+" with following rgb result: value:"+rgb+
|
||||
" red:"+red+", green:"+green+", blue:"+blue);
|
||||
return rgb;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,115 @@
|
||||
package mosaic.picture.worker;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.awt.image.ColorModel;
|
||||
import java.io.File;
|
||||
import java.util.HashMap;
|
||||
|
||||
import mosaic.log.Log;
|
||||
import mosaic.log.LogLevel;
|
||||
import mosaic.picture.ImageAnalyzer;
|
||||
import mosaic.picture.ImageUtils;
|
||||
|
||||
public class TargetImageAnalyzerWorker extends ImageAnalyzerWorker{
|
||||
|
||||
private ImageAnalyzer ia;
|
||||
private int startCoord, endCoord;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param ia ImageAnalyzer to work for
|
||||
* @param name Thread name to use (should be unique)
|
||||
* @param img Image to work on
|
||||
* @param startCoord slot x coordinate to start from
|
||||
* @param endCoord slot y coordinate to start from
|
||||
*/
|
||||
public TargetImageAnalyzerWorker(ImageAnalyzer ia, String name, int startCoord, int endCoord){
|
||||
super(ia, name);
|
||||
this.ia = ia;
|
||||
this.startCoord = startCoord;
|
||||
this.endCoord = endCoord;
|
||||
|
||||
this.start();
|
||||
}
|
||||
|
||||
public void run(){
|
||||
Log.log(LogLevel.Info, "Worker "+this.getName()+" up and running! Let's roll ...");
|
||||
HashMap<int[], Integer> result = this.classifyArea(startCoord, endCoord);
|
||||
ia.addSlotClassifications(result, this.getName());
|
||||
Log.log(LogLevel.Info, "Worker "+this.getName()+" finished work! Terminating ...");
|
||||
}
|
||||
|
||||
/**
|
||||
* Classifies the specified Area of the target image and returns the average colors.
|
||||
*
|
||||
* Returns a HashMap with the slot coordinates (slot) as key.
|
||||
* @param startCoord slotX to start in
|
||||
* @param endCoord slotY to start in
|
||||
* @param workArea number of slots to work through
|
||||
* @param target image to work on
|
||||
*/
|
||||
public HashMap<int[], Integer> classifyArea(int startCoord, int endCoord){
|
||||
BufferedImage target = ia.target;
|
||||
HashMap<int[], Integer> results = new HashMap<int[], Integer>();
|
||||
Log.log(LogLevel.Debug, "["+this.getName()+"] Was ordered to classify "+startCoord+" "+endCoord);
|
||||
for(int i = startCoord; i < endCoord; i++){
|
||||
int[] coordPixels = ImageUtils.getSlotCoordPixels(ia, ImageUtils.getSlotCoord(ia, i), true);
|
||||
if(coordPixels[0]+ia.preSlotWidth >= ia.target.getWidth() || coordPixels[1]+ia.preSlotHeight >= ia.target.getHeight()){
|
||||
//Dirty FIX
|
||||
//This will inevitably land outside the Raster otherwise. I should prevent these Coords from the start
|
||||
//Log.log(LogLevel.Error, "Didn't classify "+coordPixels[0]+" "+coordPixels[1]);
|
||||
continue;
|
||||
}
|
||||
int rgb = classifySlot(coordPixels[0], coordPixels[1], ia.preSlotWidth, ia.preSlotHeight, ia.target);
|
||||
int[] coordSlot = {coordPixels[0]/ia.preSlotWidth, coordPixels[1]/ia.preSlotHeight};
|
||||
Log.log(LogLevel.Debug, "["+this.getName()+"] Classified on slot "+coordSlot[0]+" "+coordSlot[1]+" i:"+i
|
||||
+" coordPixels:"+coordPixels[0]+"x"+coordPixels[1]+" preslotDimensions:"+ia.preSlotWidth+"x"+ia.preSlotHeight+
|
||||
" slotX:"+ia.slotX+" slotY:"+ia.slotY);
|
||||
/*if(coordPixels[0] == 210 && coordPixels[1] == 0){//TODO remove
|
||||
System.out.println("Brrrring!");
|
||||
Log.log(LogLevel.Error, "Brrrriiing"+rgb+" ");
|
||||
System.out.println(rgb);
|
||||
System.out.println(coordSlot[0]+" "+coordSlot[1]);
|
||||
}*/
|
||||
|
||||
results.put(coordSlot, rgb);
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Classifies a single specified slot on the given Image.
|
||||
* @param gridX Slot specification in X
|
||||
* @param gridY Slot specification in Y
|
||||
* @param slotWidth Slot size in x direction
|
||||
* @param slotHeight Slot size in y direction
|
||||
* @param target Image to work on
|
||||
* @return average Color as RGB value
|
||||
*/
|
||||
public int classifySlot(int gridX, int gridY, int slotWidth, int slotHeight, BufferedImage target){
|
||||
Log.log(LogLevel.Debug, "["+this.getName()+"] Slicing slot at pixels: "+gridX+"x"+gridY+" out of the target with "+slotWidth+"x"+slotHeight+"for classification ...");
|
||||
BufferedImage subImage = target.getSubimage(gridX, gridY, slotWidth, slotHeight);
|
||||
ColorModel cm = ColorModel.getRGBdefault();
|
||||
float red = 0, green = 0, blue = 0;
|
||||
int pixels = 0;
|
||||
for(int x = 0; x < subImage.getWidth(); x++){
|
||||
for(int y = 0; y < subImage.getHeight(); y++){
|
||||
int rgb = subImage.getRGB(x, y);
|
||||
red += cm.getRed(rgb);
|
||||
green += cm.getGreen(rgb);
|
||||
blue += cm.getBlue(rgb);
|
||||
pixels++;
|
||||
}
|
||||
}
|
||||
red = red/pixels;
|
||||
green = green/pixels;
|
||||
blue = blue/pixels;
|
||||
int rgb = new Color((int)red, (int)green, (int)blue).getRGB();
|
||||
Log.log(LogLevel.Debug, "["+this.getName()+"] Classified slot at pixels: "+gridX+"x"+gridY+" with following rgb result: value:"+rgb+
|
||||
" red:"+red+", green:"+green+", blue:"+blue);
|
||||
return rgb;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
package mosaic.window;
|
||||
|
||||
import java.awt.Canvas;
|
||||
import java.awt.Graphics2D;
|
||||
//import java.awt.Rectangle;
|
||||
import java.awt.image.BufferedImage;
|
||||
|
||||
public class MosaicDisplay extends Canvas{
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private static final long serialVersionUID = 2738927210561389362L;
|
||||
|
||||
private Graphics2D g2 = (Graphics2D) this.getGraphics();
|
||||
|
||||
public MosaicDisplay(int width, int height){
|
||||
this.setSize(width, height);
|
||||
}
|
||||
|
||||
public void drawImage(BufferedImage img, int x, int y){
|
||||
|
||||
g2.drawImage(img, null, x, y);
|
||||
}
|
||||
|
||||
public void clear(){
|
||||
//g2.draw(new Rectangle(this.getWidth(), this.getHeight()));
|
||||
this.invalidate();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,226 @@
|
||||
package mosaic.window;
|
||||
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.Font;
|
||||
import java.awt.GridLayout;
|
||||
|
||||
import javax.swing.BoxLayout;
|
||||
import javax.swing.JCheckBox;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JSpinner;
|
||||
import javax.swing.JTextField;
|
||||
import javax.swing.border.Border;
|
||||
|
||||
public class SettingsPanel extends JPanel{
|
||||
// Retry with SpringLayout?
|
||||
/*
|
||||
* gridWidth - NumField
|
||||
* gridHeight - NumField
|
||||
* targetMulti - NumField
|
||||
* keepRatio - CheckBox
|
||||
* overlapImages - CheckBox
|
||||
* alphaThreshhold - Spinner (0-255)
|
||||
*
|
||||
* adaptionCount - NumField
|
||||
* adaptionStep - Spinner (1.01 - 2.0)
|
||||
* gridErrorThresh - Spinner (0.01 - 1.0)
|
||||
*
|
||||
* ---PERFORMANCE---
|
||||
* inputWorkerLimit - NumField
|
||||
* targetWorkerLimit - NumField
|
||||
* matchWorkerLimit - NumField
|
||||
* placeWorkerLimit - NumField
|
||||
*
|
||||
*/
|
||||
private final static String[] guiText = {
|
||||
"Grid Width:",
|
||||
"Grid Height:",
|
||||
"Magnification:",
|
||||
"Keep Image Ratio:",
|
||||
"Overlap Images:",
|
||||
"Alpha Threshhold:",
|
||||
"Number of Adaption Steps (max):",
|
||||
"Size of Adaption Step:",
|
||||
"Grid Error Threshhold:",
|
||||
"Input Worker Limit:",
|
||||
"Target Worker Limit:",
|
||||
"Match Worker Limit:",
|
||||
"Place Worker Limit:",
|
||||
"Settings"
|
||||
};
|
||||
|
||||
private JPanel topPanel, gridPanel,
|
||||
gridWidthPanel, gridHeightPanel, targetMultiPanel,
|
||||
adaptionCountPanel, adaptionStepPanel, inputWorkerPanel,
|
||||
targetWorkerPanel, matchWorkerPanel, placeWorkerPanel,
|
||||
gridErrorPanel, alphaThreshPanel, keepRatioPanel,
|
||||
overlapPanel;
|
||||
private JLabel titleLabel;
|
||||
private JTextField gridWidthField, gridHeightField, targetMultiField,
|
||||
adaptionCountField, inputWorkerField, targetWorkerField, matchWorkerField,
|
||||
placeWorkerField;
|
||||
private JSpinner adaptionStepSpinner, gridErrorSpinner, alphaThreshSpinner;
|
||||
private JCheckBox keepRatioCheck, overlapCheck;
|
||||
|
||||
private JLabel gridWidthLabel, gridHeightLabel, targetMultiLabel,
|
||||
keepRatioLabel, overlapLabel, alphaThreshLabel, adaptionCountLabel,
|
||||
adaptionStepLabel, gridErrorLabel, inputWorkerLabel, targetWorkerLabel,
|
||||
matchWorkerLabel, placeWorkerLabel;
|
||||
|
||||
public SettingsPanel(){
|
||||
//Initiate
|
||||
gridPanel = new JPanel();
|
||||
topPanel = new JPanel();
|
||||
gridWidthPanel = new JPanel();
|
||||
gridHeightPanel = new JPanel();
|
||||
targetMultiPanel = new JPanel();
|
||||
adaptionCountPanel = new JPanel();
|
||||
adaptionStepPanel = new JPanel();
|
||||
inputWorkerPanel = new JPanel();
|
||||
targetWorkerPanel = new JPanel();
|
||||
matchWorkerPanel = new JPanel();
|
||||
placeWorkerPanel = new JPanel();
|
||||
gridErrorPanel = new JPanel();
|
||||
alphaThreshPanel = new JPanel();
|
||||
keepRatioPanel = new JPanel();
|
||||
overlapPanel = new JPanel();
|
||||
|
||||
gridWidthField = new JTextField();
|
||||
gridHeightField = new JTextField();
|
||||
targetMultiField = new JTextField();
|
||||
adaptionCountField = new JTextField();
|
||||
inputWorkerField = new JTextField();
|
||||
targetWorkerField = new JTextField();
|
||||
matchWorkerField = new JTextField();
|
||||
placeWorkerField = new JTextField();
|
||||
|
||||
adaptionStepSpinner = new JSpinner();
|
||||
gridErrorSpinner = new JSpinner();
|
||||
alphaThreshSpinner = new JSpinner();
|
||||
|
||||
keepRatioCheck = new JCheckBox();
|
||||
overlapCheck = new JCheckBox();
|
||||
|
||||
gridWidthLabel = new JLabel(guiText[0]);
|
||||
gridHeightLabel = new JLabel(guiText[1]);
|
||||
targetMultiLabel = new JLabel(guiText[2]);
|
||||
keepRatioLabel = new JLabel(guiText[3]);
|
||||
overlapLabel = new JLabel(guiText[4]);
|
||||
alphaThreshLabel = new JLabel(guiText[5]);
|
||||
adaptionCountLabel = new JLabel(guiText[6]);
|
||||
adaptionStepLabel = new JLabel(guiText[7]);
|
||||
gridErrorLabel = new JLabel(guiText[8]);
|
||||
inputWorkerLabel = new JLabel(guiText[9]);
|
||||
targetWorkerLabel = new JLabel(guiText[10]);
|
||||
matchWorkerLabel = new JLabel(guiText[11]);
|
||||
placeWorkerLabel = new JLabel(guiText[12]);
|
||||
titleLabel = new JLabel(guiText[13]);
|
||||
// End Initiate
|
||||
|
||||
//Config Component
|
||||
titleLabel.setFont(titleLabel.getFont().deriveFont(40f));
|
||||
|
||||
Dimension lineSize = new Dimension(100, 40);
|
||||
gridWidthField.setMaximumSize(lineSize);
|
||||
gridHeightField.setMaximumSize(lineSize);
|
||||
targetMultiField.setMaximumSize(lineSize);
|
||||
adaptionCountField.setMaximumSize(lineSize);
|
||||
inputWorkerField.setMaximumSize(lineSize);
|
||||
targetWorkerField.setMaximumSize(lineSize);
|
||||
matchWorkerField.setMaximumSize(lineSize);
|
||||
placeWorkerField.setMaximumSize(lineSize);
|
||||
|
||||
adaptionStepSpinner.setMaximumSize(lineSize);
|
||||
gridErrorSpinner.setMaximumSize(lineSize);
|
||||
alphaThreshSpinner.setMaximumSize(lineSize);
|
||||
|
||||
keepRatioCheck.setMaximumSize(lineSize);
|
||||
overlapCheck.setMaximumSize(lineSize);
|
||||
//End Config Components
|
||||
|
||||
// Add to Panels
|
||||
add(topPanel, BorderLayout.WEST);
|
||||
add(gridPanel, BorderLayout.WEST);
|
||||
|
||||
topPanel.add(titleLabel);
|
||||
|
||||
gridWidthPanel.add(gridWidthLabel);
|
||||
gridWidthPanel.add(gridWidthField);
|
||||
|
||||
gridHeightPanel.add(gridHeightLabel);
|
||||
gridHeightPanel.add(gridHeightField);
|
||||
|
||||
targetMultiPanel.add(targetMultiLabel);
|
||||
targetMultiPanel.add(targetMultiField);
|
||||
|
||||
keepRatioPanel.add(keepRatioLabel);
|
||||
keepRatioPanel.add(keepRatioCheck);
|
||||
|
||||
overlapPanel.add(overlapLabel);
|
||||
overlapPanel.add(overlapCheck);
|
||||
|
||||
alphaThreshPanel.add(alphaThreshLabel);
|
||||
alphaThreshPanel.add(alphaThreshSpinner);
|
||||
|
||||
adaptionCountPanel.add(adaptionCountLabel);
|
||||
adaptionCountPanel.add(adaptionCountField);
|
||||
|
||||
adaptionStepPanel.add(adaptionStepLabel);
|
||||
adaptionStepPanel.add(adaptionStepSpinner);
|
||||
|
||||
gridErrorPanel.add(gridErrorLabel);
|
||||
gridErrorPanel.add(gridErrorSpinner);
|
||||
|
||||
inputWorkerPanel.add(inputWorkerLabel);
|
||||
inputWorkerPanel.add(inputWorkerField);
|
||||
|
||||
targetWorkerPanel.add(targetWorkerLabel);
|
||||
targetWorkerPanel.add(targetWorkerField);
|
||||
|
||||
targetMultiPanel.add(targetMultiLabel);
|
||||
targetMultiPanel.add(targetMultiField);
|
||||
|
||||
matchWorkerPanel.add(matchWorkerLabel);
|
||||
matchWorkerPanel.add(matchWorkerField);
|
||||
|
||||
placeWorkerPanel.add(placeWorkerLabel);
|
||||
placeWorkerPanel.add(placeWorkerField);
|
||||
|
||||
gridPanel.add(gridWidthPanel);
|
||||
gridPanel.add(gridHeightPanel);
|
||||
gridPanel.add(targetMultiPanel);
|
||||
gridPanel.add(overlapPanel);
|
||||
gridPanel.add(keepRatioPanel);
|
||||
gridPanel.add(alphaThreshPanel);
|
||||
gridPanel.add(adaptionCountPanel);
|
||||
gridPanel.add(adaptionStepPanel);
|
||||
gridPanel.add(inputWorkerPanel);
|
||||
gridPanel.add(targetWorkerPanel);
|
||||
gridPanel.add(matchWorkerPanel);
|
||||
gridPanel.add(placeWorkerPanel);
|
||||
gridPanel.add(gridErrorPanel);
|
||||
//End Add to Panels
|
||||
|
||||
//Setup Layouts
|
||||
gridWidthPanel.setLayout(new BoxLayout(gridWidthPanel, BoxLayout.LINE_AXIS));
|
||||
gridHeightPanel.setLayout(new BoxLayout(gridHeightPanel, BoxLayout.LINE_AXIS));
|
||||
targetMultiPanel.setLayout(new BoxLayout(targetMultiPanel, BoxLayout.LINE_AXIS));
|
||||
keepRatioPanel.setLayout(new BoxLayout(keepRatioPanel, BoxLayout.LINE_AXIS));
|
||||
overlapPanel.setLayout(new BoxLayout(overlapPanel, BoxLayout.LINE_AXIS));
|
||||
alphaThreshPanel.setLayout(new BoxLayout(alphaThreshPanel, BoxLayout.LINE_AXIS));
|
||||
adaptionCountPanel.setLayout(new BoxLayout(adaptionCountPanel, BoxLayout.LINE_AXIS));
|
||||
adaptionStepPanel.setLayout(new BoxLayout(adaptionStepPanel, BoxLayout.LINE_AXIS));
|
||||
gridErrorPanel.setLayout(new BoxLayout(gridErrorPanel, BoxLayout.LINE_AXIS));
|
||||
inputWorkerPanel.setLayout(new BoxLayout(inputWorkerPanel, BoxLayout.LINE_AXIS));
|
||||
targetWorkerPanel.setLayout(new BoxLayout(targetWorkerPanel, BoxLayout.LINE_AXIS));
|
||||
targetMultiPanel.setLayout(new BoxLayout(targetMultiPanel, BoxLayout.LINE_AXIS));
|
||||
matchWorkerPanel.setLayout(new BoxLayout(matchWorkerPanel, BoxLayout.LINE_AXIS));
|
||||
placeWorkerPanel.setLayout(new BoxLayout(placeWorkerPanel, BoxLayout.LINE_AXIS));
|
||||
|
||||
gridPanel.setLayout(new GridLayout(13, 1));
|
||||
this.setLayout(new BoxLayout(this, BoxLayout.PAGE_AXIS));
|
||||
//End Setup Layouts
|
||||
}
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
package mosaic.window;
|
||||
|
||||
import java.awt.Component;
|
||||
|
||||
import javax.swing.JFrame;
|
||||
import javax.swing.JPanel;
|
||||
|
||||
import mosaic.Mosaic;
|
||||
|
||||
public class Window extends JFrame{
|
||||
|
||||
private JPanel imagePanel, settingsPanel;
|
||||
|
||||
public Window(String name, int width, int height, Component display){
|
||||
this.setSize(width, height);
|
||||
this.setTitle(name);
|
||||
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
||||
|
||||
imagePanel = new JPanel();
|
||||
settingsPanel = new SettingsPanel();
|
||||
|
||||
this.add(imagePanel);
|
||||
this.add(settingsPanel);
|
||||
|
||||
//this.pack();
|
||||
this.setVisible(true);
|
||||
}
|
||||
|
||||
public static void main(String[] args){
|
||||
int width = 800, height = 600;
|
||||
Window win = new Window(Mosaic.programmName+" "+Mosaic.versionString, width, height,
|
||||
new MosaicDisplay(width, height));
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue