/* * polycasso - Cubism Artwork generator * Copyright 2009-2017 MeBigFatGuy.com * Copyright 2009-2017 Dave Brosius * Inspired by work by Roger Alsing * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and limitations * under the License. */ package com.mebigfatguy.polycasso; import java.awt.Rectangle; import java.awt.image.BufferedImage; import java.awt.image.DataBufferByte; import java.awt.image.WritableRaster; /** * an immutable class for processing a test image against target image for closeness. */ public class DefaultFeedback implements Feedback { private byte[] targetBuffer; private int width, height; private int gridWidth, gridHeight; /** * creates a feedback object with a given targetImage. Caches the image bytes in member variables. */ public DefaultFeedback() { } /** * caches information about the target image * * @param targetImage * the target image that will be the judge of test images */ @Override public void setTargetImage(BufferedImage targetImage) { WritableRaster raster = targetImage.getRaster(); width = targetImage.getWidth(); height = targetImage.getHeight(); gridWidth = (width / DefaultScore.NUM_DIVISIONS); gridHeight = (height / DefaultScore.NUM_DIVISIONS); DataBufferByte dbb = (DataBufferByte) raster.getDataBuffer(); targetBuffer = dbb.getData(); } /** * returns a score of how close the test image is to the target which is the square of the error to the target image * * @param testImage * the image to score * @param previousScore * the score of the generated image from which this image was created * @param changedArea * the area of changed between the parent generated image and this one * * @return a score that represents its closeness to ideal */ @Override public Score calculateScore(BufferedImage testImage, Score previousScore, Rectangle changedArea) { WritableRaster raster = testImage.getRaster(); DataBufferByte dbb = (DataBufferByte) raster.getDataBuffer(); byte[] testBuffer = dbb.getData(); Score score; if ((changedArea == null) || (changedArea.width > changedArea.height)) { score = calculateYMajorScore(testBuffer, previousScore, changedArea); } else { score = calculateXMajorScore(testBuffer, previousScore, changedArea); } return score; } private Score calculateYMajorScore(byte[] testBuffer, Score previousScore, Rectangle changedArea) { DefaultScore score = (previousScore != null) ? (DefaultScore) previousScore.clone() : new DefaultScore(); boolean needFullRecalc = (previousScore == null) || (changedArea == null); score.overallScore = 0L; int gridTop = 0; for (int y = 0; y < DefaultScore.NUM_DIVISIONS; y++) { int gridBottom; if (y < (DefaultScore.NUM_DIVISIONS - 1)) { gridBottom = gridTop + gridHeight; } else { gridBottom = height; } if (needFullRecalc || ((changedArea.y <= gridBottom) && ((changedArea.y + changedArea.height) >= gridTop))) { int gridLeft = 0; for (int x = 0; x < DefaultScore.NUM_DIVISIONS; x++) { int gridRight; if (x < (DefaultScore.NUM_DIVISIONS - 1)) { gridRight = gridLeft + gridWidth; } else { gridRight = width; } if (needFullRecalc || ((changedArea.x <= gridRight) && ((changedArea.x + changedArea.width) >= gridLeft))) { long gridError = calculateGridScore(testBuffer, gridLeft, gridTop, gridRight, gridBottom); score.gridScores[x][y] = gridError; score.overallScore += gridError; } else { score.overallScore += score.gridScores[x][y]; } gridLeft = gridRight; } } else { for (int x = 0; x < DefaultScore.NUM_DIVISIONS; x++) { score.overallScore += score.gridScores[x][y]; } } gridTop = gridBottom; } return score; } private Score calculateXMajorScore(byte[] testBuffer, Score previousScore, Rectangle changedArea) { DefaultScore score = (previousScore != null) ? (DefaultScore) previousScore.clone() : new DefaultScore(); boolean needFullRecalc = (previousScore == null) || (changedArea == null); score.overallScore = 0L; int gridLeft = 0; for (int x = 0; x < DefaultScore.NUM_DIVISIONS; x++) { int gridRight; if (x < (DefaultScore.NUM_DIVISIONS - 1)) { gridRight = gridLeft + gridWidth; } else { gridRight = width; } if (needFullRecalc || ((changedArea.x <= gridRight) && ((changedArea.x + changedArea.width) >= gridLeft))) { int gridTop = 0; for (int y = 0; y < DefaultScore.NUM_DIVISIONS; y++) { int gridBottom; if (y < (DefaultScore.NUM_DIVISIONS - 1)) { gridBottom = gridTop + gridHeight; } else { gridBottom = height; } if (needFullRecalc || ((changedArea.y <= gridBottom) && ((changedArea.y + changedArea.height) >= gridTop))) { long gridError = calculateGridScore(testBuffer, gridLeft, gridTop, gridRight, gridBottom); score.gridScores[x][y] = gridError; score.overallScore += gridError; } else { score.overallScore += score.gridScores[x][y]; } gridTop = gridBottom; } } else { for (int y = 0; y < DefaultScore.NUM_DIVISIONS; y++) { score.overallScore += score.gridScores[x][y]; } } gridLeft = gridRight; } return score; } private long calculateGridScore(byte[] testBuffer, int gridLeft, int gridTop, int gridRight, int gridBottom) { long gridError = 0L; for (int gy = gridTop; gy < gridBottom; gy++) { int pixelStart = (gy * width * 4) + (gridLeft * 4); int pixelEnd = pixelStart + ((gridRight - gridLeft) * 4); // index 0 is alpha, start at 1 (blue) for (int i = pixelStart + 1; i < pixelEnd; i++) { int blue1 = targetBuffer[i] & 0x0FF; int blue2 = testBuffer[i++] & 0x0FF; long blueError = blue1 - blue2; blueError *= blueError; int green1 = targetBuffer[i] & 0x0FF; int green2 = testBuffer[i++] & 0x0FF; long greenError = green1 - green2; greenError *= greenError; int red1 = targetBuffer[i] & 0x0FF; int red2 = testBuffer[i++] & 0x0FF; long redError = red1 - red2; redError *= redError; gridError += redError + greenError + blueError; } } return gridError; } }