/* * 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.Color; import java.awt.Dimension; import java.awt.Polygon; import java.awt.Rectangle; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Random; /** * a class that applies various improvement attempts to a polygon, attempts to prioritize which algorithms to pick based on what has worked in the past, as well * as priorities which polygons have had success being transformed. */ public class Improver { private final Settings settings; private final GenerationHandler generationHandler; private final Dimension imageSize; private final Random r; private List<PolygonData> polygons = null; private Rectangle changedArea; private GenerationMember changedMember; private final ImprovementTypeStats stats; /** * create an improver using a specified image size * * @param confSettings * the settings to be used * @param genHandler * the generation handler * @param size * the size of the image */ public Improver(Settings confSettings, GenerationHandler genHandler, Dimension size) { settings = confSettings; generationHandler = genHandler; imageSize = size; stats = new ImprovementTypeStats(); r = new Random(); } /** * get the list of polygons usually after attempted to be improved * * @return the list of polygons */ public List<PolygonData> getData() { if (polygons == null) { return new ArrayList<PolygonData>(); } return Collections.<PolygonData> unmodifiableList(polygons); } /** * updates the stats for types that successfully improved the image * * @param type * the improvement type that was successful * @param successful * whether the improvement was successful */ public void typeWasSuccessful(ImprovementType type, boolean successful) { stats.typeWasSuccessful(type, successful); } /** * attempts to improve on one polygon randomly by adjusting it according to a randomly selected improvement type * * @return the improvement type used to alter the data */ public ImprovementType improveRandomly() { changedMember = generationHandler.getRandomMember(false); if (changedMember != null) { polygons = new ArrayList<PolygonData>(Arrays.<PolygonData> asList(changedMember.getData().clone())); } else { polygons = new ArrayList<PolygonData>(); } ImprovementType type = (polygons.isEmpty()) ? ImprovementType.AddPolygon : stats.getRandomImprovementType(); switch (type) { case AddPolygon: { if (polygons.size() < settings.getMaxPolygons()) { PolygonData pd = PolygonData.randomPoly(imageSize, settings.getMaxPoints()); polygons.add(pd); changedArea = pd.getPolygon().getBounds(); } else { randomCompleteChange(); type = ImprovementType.CompleteChange; typeWasSuccessful(ImprovementType.AddPolygon, false); } } break; case RemovePolygon: { if (polygons.size() > 0) { int idx = r.nextInt(polygons.size()); changedArea = polygons.get(idx).getPolygon().getBounds(); polygons.remove(idx); } } break; case AddPoint: { int idx = r.nextInt(polygons.size()); PolygonData pd = polygons.get(idx).clone(); Polygon polygon = pd.getPolygon(); changedArea = polygon.getBounds(); if (polygon.npoints < settings.getMaxPoints()) { polygon.addPoint(0, 0); int insPos = r.nextInt(polygon.npoints); int lastPt = ((insPos + polygon.npoints) - 1) % polygon.npoints; int maxMovement = settings.getMaxPtMovement(); int maxX = Math.max(maxMovement, Math.abs(polygon.xpoints[lastPt] - polygon.xpoints[insPos])); int maxY = Math.max(maxMovement, Math.abs(polygon.ypoints[lastPt] - polygon.ypoints[insPos])); int x = r.nextInt(maxX) + Math.min(polygon.xpoints[lastPt], polygon.xpoints[insPos]); int y = r.nextInt(maxY) + Math.min(polygon.ypoints[lastPt], polygon.ypoints[insPos]); int numCopyPts = polygon.npoints - insPos - 1; System.arraycopy(polygon.xpoints, insPos, polygon.xpoints, insPos + 1, numCopyPts); polygon.xpoints[insPos] = x; System.arraycopy(polygon.ypoints, insPos, polygon.ypoints, insPos + 1, numCopyPts); polygon.ypoints[insPos] = y; polygon.invalidate(); changedArea = changedArea.union(polygon.getBounds()); polygons.set(idx, pd); } else { randomCompleteChange(); type = ImprovementType.CompleteChange; typeWasSuccessful(ImprovementType.AddPoint, false); } } break; case RemovePoint: { int idx = r.nextInt(polygons.size()); PolygonData pd = polygons.get(idx).clone(); Polygon polygon = pd.getPolygon(); changedArea = polygon.getBounds(); if (polygon.npoints > 3) { int delPos = r.nextInt(polygon.npoints); int numPtCopy = polygon.npoints - delPos - 1; System.arraycopy(polygon.xpoints, delPos + 1, polygon.xpoints, delPos, numPtCopy); System.arraycopy(polygon.ypoints, delPos + 1, polygon.ypoints, delPos, numPtCopy); polygon.npoints--; polygon.invalidate(); changedArea = changedArea.union(polygon.getBounds()); polygons.set(idx, pd); } else { randomCompleteChange(); type = ImprovementType.CompleteChange; typeWasSuccessful(ImprovementType.RemovePoint, false); } } break; case MovePoint: { int idx = r.nextInt(polygons.size()); PolygonData pd = polygons.get(idx).clone(); Polygon polygon = pd.getPolygon(); changedArea = polygon.getBounds(); int movePos = r.nextInt(polygon.npoints); int maxMovement = settings.getMaxPtMovement(); int dblMax = maxMovement << 1; int moveX = r.nextInt(dblMax) - maxMovement; int moveY = r.nextInt(dblMax) - maxMovement; polygon.xpoints[movePos] += moveX; polygon.ypoints[movePos] += moveY; polygon.xpoints[movePos] = clipToRange(0, imageSize.width, polygon.xpoints[movePos]); polygon.ypoints[movePos] = clipToRange(0, imageSize.height, polygon.ypoints[movePos]); polygon.invalidate(); changedArea = changedArea.union(polygon.getBounds()); polygons.set(idx, pd); } break; case RectifyPoint: { int idx = r.nextInt(polygons.size()); PolygonData pd = polygons.get(idx).clone(); Polygon polygon = pd.getPolygon(); changedArea = polygon.getBounds(); int rectifyPos = r.nextInt(polygon.npoints); int targetPos = (rectifyPos == 0) ? polygon.npoints - 1 : (rectifyPos - 1); if (Math.abs(polygon.xpoints[rectifyPos] - polygon.xpoints[targetPos]) < Math.abs(polygon.ypoints[rectifyPos] - polygon.ypoints[targetPos])) { polygon.xpoints[rectifyPos] = polygon.xpoints[targetPos]; } else { polygon.ypoints[rectifyPos] = polygon.ypoints[targetPos]; } polygon.invalidate(); changedArea = changedArea.union(polygon.getBounds()); polygons.set(idx, pd); } break; case ReorderPoly: { if (polygons.size() > 2) { PolygonData pd = polygons.remove(r.nextInt(polygons.size())); changedArea = pd.getPolygon().getBounds(); polygons.add(r.nextInt(polygons.size()), pd); } else { randomCompleteChange(); type = ImprovementType.CompleteChange; typeWasSuccessful(ImprovementType.ReorderPoly, false); } } break; case ShrinkPoly: { int idx = r.nextInt(polygons.size()); PolygonData pd = polygons.get(idx).clone(); Polygon polygon = pd.getPolygon(); changedArea = polygon.getBounds(); Rectangle bbox = polygon.getBounds(); double midX = bbox.getCenterX(); double midY = bbox.getCenterY(); int shrinkFactor = r.nextInt(settings.getMaxPtMovement()); for (int i = 0; i < polygon.npoints; i++) { polygon.xpoints[i] += (polygon.xpoints[i] < midX) ? shrinkFactor : -shrinkFactor; polygon.ypoints[i] += (polygon.ypoints[i] < midY) ? shrinkFactor : -shrinkFactor; } polygon.invalidate(); changedArea = changedArea.union(polygon.getBounds()); polygons.set(idx, pd); } break; case EnlargePoly: { int idx = r.nextInt(polygons.size()); PolygonData pd = polygons.get(idx).clone(); Polygon polygon = pd.getPolygon(); changedArea = polygon.getBounds(); Rectangle bbox = polygon.getBounds(); double midX = bbox.getCenterX(); double midY = bbox.getCenterY(); int expandFactor = r.nextInt(settings.getMaxPtMovement()); for (int i = 0; i < polygon.npoints; i++) { polygon.xpoints[i] += (polygon.xpoints[i] < midX) ? -expandFactor : expandFactor; polygon.ypoints[i] += (polygon.ypoints[i] < midY) ? -expandFactor : expandFactor; polygon.xpoints[i] = clipToRange(0, imageSize.width, polygon.xpoints[i]); polygon.ypoints[i] = clipToRange(0, imageSize.height, polygon.ypoints[i]); } polygon.invalidate(); changedArea = changedArea.union(polygon.getBounds()); polygons.set(idx, pd); } break; case ShiftPoly: { int idx = r.nextInt(polygons.size()); PolygonData pd = polygons.get(idx).clone(); Polygon polygon = pd.getPolygon(); changedArea = polygon.getBounds(); int maxMovement = settings.getMaxPtMovement(); int dblMax = maxMovement << 1; int shiftX = r.nextInt(dblMax) + maxMovement; int shiftY = r.nextInt(dblMax) + maxMovement; for (int i = 0; i < polygon.npoints; i++) { polygon.xpoints[i] += shiftX; polygon.ypoints[i] += shiftY; polygon.xpoints[i] = clipToRange(0, imageSize.width, polygon.xpoints[i]); polygon.ypoints[i] = clipToRange(0, imageSize.height, polygon.ypoints[i]); } polygon.invalidate(); changedArea = changedArea.union(polygon.getBounds()); polygons.set(idx, pd); } break; case ChangeColor: { int idx = r.nextInt(polygons.size()); PolygonData pd = polygons.get(idx).clone(); changedArea = pd.getPolygon().getBounds(); Color color = pd.getColor(); int comp = r.nextInt(3); int maxChange = settings.getMaxColorChange(); int dblChange = maxChange << 1; switch (comp) { case 0: { int newColor = color.getRed() + (r.nextInt(dblChange) - maxChange); newColor = clipToRange(0, 255, newColor); pd.setColor(new Color(newColor, color.getGreen(), color.getBlue())); } break; case 1: { int newColor = color.getGreen() + (r.nextInt(dblChange) - maxChange); newColor = clipToRange(0, 255, newColor); pd.setColor(new Color(color.getRed(), newColor, color.getBlue())); } break; case 2: { int newColor = color.getBlue() + (r.nextInt(dblChange) - maxChange); newColor = clipToRange(0, 255, newColor); pd.setColor(new Color(color.getRed(), color.getGreen(), newColor)); } break; } polygons.set(idx, pd); } break; case White: { int idx = r.nextInt(polygons.size()); PolygonData pd = polygons.get(idx).clone(); changedArea = pd.getPolygon().getBounds(); pd.setColor(Color.WHITE); pd.setAlpha(1); polygons.set(idx, pd); } break; case Black: { int idx = r.nextInt(polygons.size()); PolygonData pd = polygons.get(idx).clone(); changedArea = pd.getPolygon().getBounds(); pd.setColor(Color.BLACK); pd.setAlpha(1); polygons.set(idx, pd); } break; case ChangeAlpha: int idx = r.nextInt(polygons.size()); PolygonData pd = polygons.get(idx).clone(); changedArea = pd.getPolygon().getBounds(); pd.setAlpha(r.nextFloat()); polygons.set(idx, pd); break; case Breed: { GenerationMember copyMember = generationHandler.getRandomMember(false); if ((copyMember == null) || (copyMember.getData().length == 0)) { randomCompleteChange(); } else { PolygonData[] copyData = copyMember.getData(); idx = r.nextInt(copyData.length); if (idx >= polygons.size()) { polygons.add(copyData[idx]); changedArea = copyData[idx].getPolygon().getBounds(); } else { changedArea = polygons.get(idx).getPolygon().getBounds(); polygons.set(idx, copyData[idx]); changedArea = changedArea.union(polygons.get(idx).getPolygon().getBounds()); } } } break; case BreedElite: { GenerationMember copyMember = generationHandler.getRandomMember(true); if ((copyMember == null) || (copyMember.getData().length == 0)) { randomCompleteChange(); } else { PolygonData[] copyData = copyMember.getData(); idx = r.nextInt(copyData.length); if (idx >= polygons.size()) { polygons.add(copyData[idx]); changedArea = copyData[idx].getPolygon().getBounds(); } else { changedArea = polygons.get(idx).getPolygon().getBounds(); polygons.set(idx, copyData[idx]); changedArea = changedArea.union(polygons.get(idx).getPolygon().getBounds()); } } } break; case CompleteChange: { randomCompleteChange(); } break; } return type; } /** * returns the member image from which this generated image was created * * @return the parent generated image member */ public GenerationMember getParentGenerationMember() { return changedMember; } /** * returns the area that was changed in this image * * @return the rectangular bounds that was changed, or null for a complete change */ public Rectangle getChangedArea() { return changedArea; } /** * generates a random polygon change (all values) */ private void randomCompleteChange() { int idx = r.nextInt(polygons.size()); changedArea = polygons.get(idx).getPolygon().getBounds(); PolygonData randomPoly = PolygonData.randomPoly(imageSize, settings.getMaxPoints()); changedArea = changedArea.union(randomPoly.getPolygon().getBounds()); polygons.set(idx, randomPoly); } /** * clip a value between a min and max value * * @param min * the min value * @param max * the max value * @param value * the value to clip * * @return the clipped value */ private static int clipToRange(int min, int max, int value) { if (value < min) { return min; } else if (value > max) { return max; } return value; } }