diff --git a/src/peery/Mosaic.java b/src/peery/Mosaic.java index 94475df..2d8d7ee 100644 --- a/src/peery/Mosaic.java +++ b/src/peery/Mosaic.java @@ -1,32 +1,39 @@ package peery; -import java.awt.image.BufferedImage; import java.io.File; -import java.util.ArrayList; -import java.util.HashMap; import peery.file.FileHandler; import peery.log.Log; import peery.log.LogLevel; import peery.picture.ImageAnalyzer; -public class Mosaic { +public class Mosaic extends Thread{ - public static final String versionString = "Alpha-0.21"; + public static final String programmName = "Mosaic", + versionString = "Alpha-0.32"; private static final String outputName = "Output"; - private static final int gridX = 100, gridY = 100, targetMulti = 4, + private static final int gridX = 200, gridY = 200, targetMulti = 2, alphaThreshhold = 30, - adaptionCount = 300; + adaptionCount = 300, + inputWorkerLimit = 10, + targetWorkerLimit = 4, + matchWorkerLimit = 10, + placeWorkerLimit = 2; private static final double adaptionStep = 1.1, gridErrorThresh = 0.15; /* + * + * Performance: + * + * * FIX: - * TODO investigate picture stretching -> is ImageUtils.resizeImage() even used? - * TODO alphaThreshhold is currently dead + * somewhere I'm loosing pixels from the target (the output is cut off as if it was smaller) + * investigate picture stretching -> is ImageUtils.resizeImage() even used? + * alphaThreshhold is currently dead * * Feature: - * TODO explore keeping Input Image Ratio's - * TODO explore guarantee of usage of at least once, each image. + * explore keeping Input Image Ratio's + * explore guarantee of usage of at least once, each image. */ public FileHandler fh; @@ -34,93 +41,73 @@ public class Mosaic { public Mosaic(){ fh = new FileHandler("resources"); - Log.log(LogLevel.Info, "Starting Mosaic "+versionString); - ia = new ImageAnalyzer(fh); + Log.log(LogLevel.Info, "Starting "+programmName+" "+versionString); + ia = new ImageAnalyzer(fh, inputWorkerLimit, targetWorkerLimit, matchWorkerLimit, placeWorkerLimit, alphaThreshhold); + + this.start(); + } + + public void run(){ + Log.log.perfLog("Started "+programmName+" v."+versionString); + Log.log.perfLog("Starting indexing ..."); + prepMatching(); + //Log.log(LogLevel.Error, ia.slotClassifications.toString()+" prep:"+ia.isPrepInProgress()); + //Log.log(LogLevel.Error, ia.slotClassifications.toString()+" prep:"+ia.isPrepInProgress()); + + 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(); + } + } + 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(); + } + + /** + * Prepares the assembly of the Mosaic. + * + * Starts threads to index all not indexed images and rasterizes and classifies the Target. + */ + public void prepMatching(){ + ia.updateIndex(); + ia.rasterizeTarget(gridX, gridY, targetMulti, gridErrorThresh, adaptionCount, adaptionStep); + ia.classifyTarget(); + } + + public void prepAssembly(){ + ia.reloadIndex(); + ia.calculateMatches(); } public void createMosaic(){ - ia.rasterizeTarget(gridX, gridY, targetMulti, gridErrorThresh, adaptionCount, adaptionStep); - HashMap index = fh.loadIndex(); - - //Loading index - if(index == null){ - Log.log(LogLevel.Info, "No index loaded. Working with empty one ..."); - index = new HashMap(); - } - // - //Preparing Setup - ArrayList fileList = fh.listInputFiles(); - if(fileList.size() == 0){ - Log.log(LogLevel.Error, "No files in Input folder! There NEED to be at least some Images! Exiting..."); - System.exit(1); - } - Log.log(LogLevel.Info, "Starting classification of Input now ..."); - int count = 0; - for(String path: fileList){ - Log.log(LogLevel.Info, "Processing file "+(count++)+"/"+fileList.size()+" ..."); - File file = new File(path); - BufferedImage img = fh.loadImage(file); - if(img == null){ - continue; - } - int rgb = ia.classifyImage(img, file, alphaThreshhold); - fh.appendToIndex(index, file, rgb); - } - Log.log(LogLevel.Info, "Finished classification. Index is ready for production. Reloading index ..."); - index = fh.loadIndex(); - if(index == null){ - Log.log(LogLevel.Error, "No index after Classification of Input! Can't continue! Exiting..."); - System.exit(1); - } - - Log.log(LogLevel.Debug, "Canvas is "+ia.canvas.getWidth()+"x"+ia.canvas.getHeight()+" big."); - Log.log(LogLevel.Debug, "Grid will span "+ia.slotWidth*gridX+"x"+ia.slotHeight*gridY+" ."); - Log.log(LogLevel.Info, "Starting classification of target slots."); - count = 0; - int maxSlot = ia.slotX*ia.slotY; - int[][] slotRGBs = new int[ia.slotX][ia.slotY]; - for(int x = 0; x < ia.slotX; x++){ - for(int y = 0; y < ia.slotY; y++){ - Log.log(LogLevel.Info, "Processing slot "+(count++)+"/"+maxSlot+" ..."); - slotRGBs[x][y] = ia.classifySlot(x, y, ia.target); - } - } - Log.log(LogLevel.Info, "Finished classification of target slots."); - - Log.log(LogLevel.Info, "Matching indexed Images on slots ..."); - count = 0; - ArrayList fileHolder = new ArrayList(); - ArrayList imgHolder = new ArrayList(); - for(int x = 0; x < slotRGBs.length; x++){ - for(int y = 0; y < slotRGBs[x].length; y++){ - Log.log(LogLevel.Info, "Matching slot "+(count++)+"/"+maxSlot+" ..."); - HashMap matches = ia.matchClosestIndex(index, slotRGBs[x][y], 0); - String match = matches.keySet().toArray()[0].toString(); - File file = new File(fh.InputImagesFolder+fh.fs+match); - - /* - BufferedImage plain = new BufferedImage(ia.slotWidth, ia.slotHeight, BufferedImage.TYPE_INT_RGB); - Graphics2D g2 = (Graphics2D) plain.getGraphics(); - g2.setColor(new Color(slotRGBs[x][y])); - g2.fillRect(0, 0, plain.getWidth(), plain.getHeight()); - g2.dispose(); - ia.placeImage(x, y, plain); - */ - BufferedImage img; - if(!fileHolder.contains(file)){ - fileHolder.add(file); - img = fh.loadImage(file); - imgHolder.add(img); - }else{ - int i = fileHolder.indexOf(file); - img = imgHolder.get(i); - } - ia.placeImage(x, y, img); - } - } - Log.log(LogLevel.Info, "Finished matching. Output is done ..."); - fh.saveImage(ia.canvas, new File(fh.OutputFolder+fh.fs+outputName+"-"+fh.OutputFolder.listFiles().length+".png")); + Log.log(LogLevel.Info, "Starting Creation of Mosaic !"); + ia.placeFragments(); } public static void main(String[] args){ diff --git a/src/peery/file/FileHandler.java b/src/peery/file/FileHandler.java index d81a84a..f62cdef 100644 --- a/src/peery/file/FileHandler.java +++ b/src/peery/file/FileHandler.java @@ -140,13 +140,15 @@ public class FileHandler { } } - public void appendToIndex(HashMap index, File file, int rgb){ - try { - Log.log(LogLevel.Debug, "Checking index entries for "+file.getName()); - if(!indexFile.createNewFile() && loadIndexEntry(index, file) != null){ - Log.log(LogLevel.Debug, "An Entry seems to already exist for "+file.getName()); - return; - } + /** + * 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(); @@ -184,7 +186,13 @@ public class FileHandler { */ public HashMap loadIndex(){ if(!this.indexFile.exists()){ - Log.log(LogLevel.Info, "No Index file found. Nothing to load then..."); + 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 indexData = new HashMap(); @@ -200,6 +208,7 @@ public class FileHandler { 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; } diff --git a/src/peery/log/Log.java b/src/peery/log/Log.java index 1b1a082..4db53ce 100644 --- a/src/peery/log/Log.java +++ b/src/peery/log/Log.java @@ -4,13 +4,14 @@ import java.io.BufferedWriter; import java.io.File; import java.io.FileWriter; import java.io.IOException; +import java.util.ArrayList; import peery.file.FileHandler; public class Log { public static Log log; - public static final boolean silenceDebug = true, + public static final boolean silenceDebug = false, appendEvents = false, appendErrors = false; private final static String readmeText = @@ -25,13 +26,17 @@ public class Log { private String location; public final File eventFile, errorFile; - private BufferedWriter eventWriter, errorWriter; + private File perfFile; + private BufferedWriter eventWriter, errorWriter, perfWriter; + private ArrayList 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(); try { if(!this.eventFile.exists()){ @@ -40,15 +45,20 @@ public class Log { 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 void initLog(String location, FileHandler fh){ + public static synchronized void initLog(String location, FileHandler fh){ if(Log.log != null){ return; } @@ -70,15 +80,18 @@ public class Log { } public static void log(int logLvl, String message){ - Log.log.logs(logLvl, 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 void logs(int logLvl, String message){ + public synchronized void logs(int logLvl, String message){ String prefix = LogLevel.values()[logLvl].toString(); prefix = "["+prefix+"]"; BufferedWriter logWriter; @@ -105,6 +118,41 @@ public class Log { } } + 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()); diff --git a/src/peery/picture/ImageAnalyzer.java b/src/peery/picture/ImageAnalyzer.java index 0ca44a2..a2a3bf5 100644 --- a/src/peery/picture/ImageAnalyzer.java +++ b/src/peery/picture/ImageAnalyzer.java @@ -7,41 +7,297 @@ 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 peery.file.FileHandler; import peery.log.Log; import peery.log.LogLevel; +import peery.picture.worker.PlacementWorker; +import peery.picture.worker.PlainImageAnalyzerWorker; +import peery.picture.worker.MatchWorker; +import peery.picture.worker.TargetImageAnalyzerWorker; public class ImageAnalyzer { public BufferedImage target; - private FileHandler fh; + public FileHandler fh; private Dimension /*biggestInputSize,*/ targetSize; - public int slotWidth, slotHeight, slotX, slotY; + public int preSlotWidth, preSlotHeight, postSlotWidth, postSlotHeight, slotX, slotY, slotCount; public BufferedImage canvas; + private HashMap index; - public ImageAnalyzer(FileHandler fh){ + private int alphaThreshhold; + + //Input Classification Worker + private int inputWorkersLimit; + private static final String inputWorkerName = "inputWorker"; + private PlainImageAnalyzerWorker[] inputWorkers; + private ArrayList inputFiles; + // + + //Target Classification Worker + private int targetWorkersLimit; + private static final String targetWorkerName = "targetWorker"; + private TargetImageAnalyzerWorker[] targetWorkers; + public HashMap slotClassifications; //TODO change back private + // + + //Matching Worker + private int matchWorkersLimit; + private static final String matchWorkerName = "matchWorker"; + private MatchWorker[] matchWorkers; + private HashMap 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){ this.fh = fh; this.target = fh.loadImage(fh.TargetImageFile); + this.alphaThreshhold = alphaThreshhold; + + this.inputWorkersLimit = inputWorkersLimit; + this.targetWorkersLimit = targetWorkersLimit; + this.matchWorkersLimit = matchWorkersLimit; + + this.slotClassifications = new HashMap(); + this.fileMap = new HashMap(); + this.inputFiles = new ArrayList(); + + this.inputWorkers = new PlainImageAnalyzerWorker[inputWorkersLimit]; + this.targetWorkers = new TargetImageAnalyzerWorker[targetWorkersLimit]; + this.matchWorkers = new MatchWorker[matchWorkersLimit]; + this.index = fh.loadIndex(); } - public void rasterizeTarget(int gridX, int gridY, float targetSizeMultiplier, double gridErrorThresh, int adaptionThreshhold, double adaptionStep){ + /** + * 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]; + 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; + } + 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 clFragment){ + for(int[] key: clFragment.keySet()){ + if(!slotClassifications.containsKey(ImageUtils.parseCoord(key))){ + this.slotClassifications.put(ImageUtils.parseCoord(key), clFragment.get(key)); + /*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, "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 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> coordMap = new HashMap>(); + for(int[] coord: fileMap.keySet()){ + File file = fileMap.get(coord)[0]; + + if(!coordMap.containsKey(file)){ + coordMap.put(file, new ArrayList()); + } + 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()); - //TMP int count = 0; DecimalFormat df = new DecimalFormat("#"); while(true){ - double realSlotWidth = ((targetSize.width*targetSizeMultiplier)/gridX); - double realSlotHeight = ((targetSize.height*targetSizeMultiplier)/gridY); - Log.log(LogLevel.Debug, "Perfect slot Size would be "+realSlotWidth+"x"+realSlotHeight); - double widthLoss = Math.abs(realSlotWidth - Double.parseDouble(df.format(realSlotWidth))); - double heightLoss = Math.abs(realSlotHeight - Double.parseDouble(df.format(realSlotHeight))); + 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){ @@ -52,7 +308,7 @@ public class ImageAnalyzer { } 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 > adaptionThreshhold){ + if(count > adaptionCount){ Log.log(LogLevel.Critical, "Could not adapt to grid misplacement error! The result might be cut off or missing parts!"); break; } @@ -63,74 +319,25 @@ public class ImageAnalyzer { } - slotWidth = (int) ((targetSize.width*targetSizeMultiplier)/gridX); - slotHeight = (int) ((targetSize.height*targetSizeMultiplier)/gridY); + 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 are "+slotWidth+"x"+slotHeight+" big."); + Log.log(LogLevel.Debug, "Slots are "+preSlotWidth+"x"+preSlotHeight+" 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(slotWidth*gridX, slotHeight*gridY, 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()); } - /** - * 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 matchClosestIndex(HashMap index, int rgb, int deviation){ - 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. Loading it from file ..."); - index = this.fh.loadIndex(); - } - HashMap metrics = new HashMap(); - 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 matches = new HashMap(); - 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; - } - /** * Places the given input in its specified slot (gridX, gridY) on the canvas. * Assumes and checks boundaries calculated by rasterizeTarget(). @@ -140,75 +347,19 @@ public class ImageAnalyzer { * @param canvas * @return */ - public void placeImage(int gridX, int gridY, BufferedImage input){ + public synchronized void placeImage(int gridX, int gridY, BufferedImage input){ assert(gridX < slotX && gridY < slotY); - assert(input.getWidth() < slotWidth && input.getHeight() < slotHeight); + assert(input.getWidth() < postSlotWidth && input.getHeight() < postSlotHeight); Graphics2D g2 = (Graphics2D)canvas.getGraphics(); - g2.drawImage(input, gridX*slotWidth, gridY*slotHeight, slotWidth, slotHeight, null); + g2.drawImage(input, gridX*postSlotWidth, gridY*postSlotHeight, postSlotWidth, postSlotHeight, null); g2.dispose(); + //Log.log(LogLevel.Error, "Drawn picture at "+gridX*postSlotWidth+" "+gridY*postSlotHeight+" with "+input.getWidth()+"x"+input.getHeight()); } - /** - * 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; - } - public int classifySlot(int gridX, int gridY, BufferedImage target){ - int slotWidth = target.getWidth()/slotX, - slotHeight = target.getHeight()/slotY; - BufferedImage subImage = target.getSubimage(gridX*slotWidth, gridY*slotHeight, slotWidth, slotHeight); - Log.log(LogLevel.Debug, "Slicing slot "+gridX+"x"+gridY+" out of the target for classification ..."); - 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, "Classified slot "+gridX+"x"+gridY+" with following rgb result: value:"+rgb+ - " red:"+red+", green:"+green+", blue:"+blue); - return rgb; - } + + /* public static void main(String[] args){ System.out.println("ZHE DEBUG"); diff --git a/src/peery/picture/ImageUtils.java b/src/peery/picture/ImageUtils.java index 9174388..9107f0a 100644 --- a/src/peery/picture/ImageUtils.java +++ b/src/peery/picture/ImageUtils.java @@ -37,5 +37,32 @@ public class ImageUtils { return img; } } - + + /** + * 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[] getSlotCoord(ImageAnalyzer ia, int num, boolean preMagnification){ + 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 ySlots = num/(ia.slotY-1); + int xSlots = num%(ia.slotX-1); + int[] coords = {xSlots*slotWidth, ySlots*slotHeight}; + return coords; + } + + public static String parseCoord(int[] coord){ + return coord[0]+"-"+coord[1]; + } } diff --git a/src/peery/picture/worker/ImageAnalyzerWorker.java b/src/peery/picture/worker/ImageAnalyzerWorker.java new file mode 100644 index 0000000..08532f5 --- /dev/null +++ b/src/peery/picture/worker/ImageAnalyzerWorker.java @@ -0,0 +1,23 @@ +package peery.picture.worker; + +import peery.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; + } + +} diff --git a/src/peery/picture/worker/MatchWorker.java b/src/peery/picture/worker/MatchWorker.java new file mode 100644 index 0000000..bb8eccb --- /dev/null +++ b/src/peery/picture/worker/MatchWorker.java @@ -0,0 +1,125 @@ +package peery.picture.worker; + +import java.awt.image.ColorModel; +import java.io.File; +import java.util.HashMap; + +import peery.log.Log; +import peery.log.LogLevel; +import peery.picture.ImageAnalyzer; +import peery.picture.ImageUtils; + +public class MatchWorker extends ImageAnalyzerWorker{ + + private int startCoord, endCoord, deviation; + HashMap slotClassifications; + HashMap index; + + public MatchWorker(ImageAnalyzer ia, String name, int startCoord, int endCoord, int deviation, + HashMap slotClassifications, HashMap 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 fileMap = new HashMap(); + 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() -> slotClass.get(ImageUtils.parse(coord)==null"); + Log.log(LogLevel.Error, "BRrrring"+slotClassifications.get(ImageUtils.parseCoord(coord))); + Log.log(LogLevel.Error, "parsed: "+ImageUtils.parseCoord(coord)); + Log.log(LogLevel.Error, ""+coord[0]+" "+coord[1]); + Log.log(LogLevel.Error, ""); + for(String key: slotClassifications.keySet()){ + //Log.log(LogLevel.Error, key); + } + } + HashMap 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 matchClosestIndex(HashMap 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 metrics = new HashMap(); + 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 matches = new HashMap(); + 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; + } + +} diff --git a/src/peery/picture/worker/PlacementWorker.java b/src/peery/picture/worker/PlacementWorker.java new file mode 100644 index 0000000..778ba1d --- /dev/null +++ b/src/peery/picture/worker/PlacementWorker.java @@ -0,0 +1,47 @@ +package peery.picture.worker; + +import java.awt.image.BufferedImage; +import java.io.File; +import java.util.ArrayList; +import java.util.HashMap; + +import peery.log.Log; +import peery.log.LogLevel; +import peery.picture.ImageAnalyzer; + +public class PlacementWorker extends ImageAnalyzerWorker{ + + private HashMap> coordMap; + + public PlacementWorker(ImageAnalyzer ia, String name, HashMap> 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){ + continue; + } + BufferedImage img = ia.fh.loadImage(file); + ArrayList coords = coordMap.get(file); + + 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); + 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 ..."); + } + +} diff --git a/src/peery/picture/worker/PlainImageAnalyzerWorker.java b/src/peery/picture/worker/PlainImageAnalyzerWorker.java new file mode 100644 index 0000000..9167ade --- /dev/null +++ b/src/peery/picture/worker/PlainImageAnalyzerWorker.java @@ -0,0 +1,90 @@ +package peery.picture.worker; + +import java.awt.Color; +import java.awt.image.BufferedImage; +import java.awt.image.ColorModel; +import java.io.File; + +import peery.log.Log; +import peery.log.LogLevel; +import peery.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; + } + +} diff --git a/src/peery/picture/worker/TargetImageAnalyzerWorker.java b/src/peery/picture/worker/TargetImageAnalyzerWorker.java new file mode 100644 index 0000000..8ce808e --- /dev/null +++ b/src/peery/picture/worker/TargetImageAnalyzerWorker.java @@ -0,0 +1,114 @@ +package peery.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 peery.log.Log; +import peery.log.LogLevel; +import peery.picture.ImageAnalyzer; +import peery.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 result = this.classifyArea(startCoord, endCoord); + ia.addSlotClassifications(result); + 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 classifyArea(int startCoord, int endCoord){ + BufferedImage target = ia.target; + HashMap results = new HashMap(); + System.out.println("Was ordered: "+startCoord+" "+endCoord); + for(int i = startCoord; i < endCoord; i++){ + int[] coordPixels = 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}; + + /*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){ + System.out.println(gridX+" "+gridY+" "+slotWidth+" "+slotHeight); + Log.log(LogLevel.Debug, "["+this.getName()+"] Slicing slot "+gridX+"x"+gridY+" out of the target 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 "+gridX+"x"+gridY+" with following rgb result: value:"+rgb+ + " red:"+red+", green:"+green+", blue:"+blue); + return rgb; + } + +}