Class ImageApproximationProblem
- All Implemented Interfaces:
GAProblem<PolygonChromosome>
GAProblem that evolves a population of PolygonChromosome
instances to approximate a target image using semi-transparent triangles.
Fitness
Color fitness is the negated mean squared error (MSE) between the rendered chromosome and target RGB channels:
fitness = −MSE MSE = Σ [(ΔR² + ΔG² + ΔB²) / 255²] / (pixels × 3)
Following the GA convention of higher = better, a perfect match has fitness
0.0 and a maximally wrong solution has fitness -1.0. The
alpha channel is excluded from the error metric — it affects appearance
through blending but is not directly penalized.
ImageFitnessMode.LINE_AWARE blends 70% of this color error with
30% normalized Sobel luminance-edge error. This rewards candidates whose
major contours align with the target even before their colors are exact.
Performance
Three optimizations keep fitness evaluation fast enough for interactive population sizes (≥ 100 individuals):
- Reduced fitness resolution. The target image is pre-scaled to
FITNESS_W×FITNESS_Hpixels at construction. All fitness evaluations render at this small size. The view callsPolygonChromosome.render(int, int)at full display resolution independently. Typical speedup: 6–10× over full-resolution evaluation. - Thread-local render buffers. The
BufferedImageand pixel array used during eachfitness()call are allocated once per thread and reused for every subsequent call on that thread. This eliminates per-evaluation heap allocation and GC pressure. The buffers are stored inThreadLocalfields, keeping this fitness implementation safe if a caller evaluates individuals concurrently. Typical speedup: 2–3× over per-call allocation. - Integer MSE accumulation. The inner pixel loop accumulates squared
channel differences as
longintegers and divides by255²exactly once after the loop, avoiding per-pixel floating-point division. Typical speedup: 1.1–1.2×.
Usage
BufferedImage target = ImageIO.read(new File("mona_lisa.png"));
GAProblem<PolygonChromosome> problem = new ImageApproximationProblem(target, 50);
GAOperators<PolygonChromosome> ops = new GAOperators<>(new TournamentSelection<>(5), new UniformBlendCrossover(),
new GaussianMutation(0.05), new ElitistReplacement<>(2));
GeneticAlgorithmSimulation<PolygonChromosome> sim = new GeneticAlgorithmSimulation<>(problem, GAConfig.defaults(),
ops);
-
Field Summary
Fields -
Constructor Summary
ConstructorsConstructorDescriptionImageApproximationProblem(BufferedImage target, int numTriangles) Construct an image approximation problem for the given target image and triangle count.ImageApproximationProblem(BufferedImage target, int numTriangles, ImageFitnessMode fitnessMode) Construct a problem with an explicit fitness metric. -
Method Summary
Modifier and TypeMethodDescriptiondoubleCompute fitness as the negated configured error between the rendered image and target.intReturn the original target image height in pixels.intReturn the original target image width in pixels.intReturn the number of triangles per chromosome for this problem.initialPopulation(int size, Random rng) Generate an initial population ofsizerandomly initialized chromosomes.randomIndividual(Random rng) Generate a random chromosome in which all gene values are drawn independently fromUniform(0, 1).
-
Field Details
-
FITNESS_W
public static final int FITNESS_WWidth in pixels used for fitness evaluation rendering.Reducing this value increases evaluation speed at the cost of a coarser fitness signal. Values in the range 60–100 work well for triangle counts up to ~100. The view always renders at the original image dimensions via
PolygonChromosome.render(int, int).- See Also:
-
FITNESS_H
public static final int FITNESS_HHeight in pixels used for fitness evaluation rendering.- See Also:
-
-
Constructor Details
-
ImageApproximationProblem
Construct an image approximation problem for the given target image and triangle count.The target image is immediately scaled to
FITNESS_W×FITNESS_Husing bilinear interpolation and its pixel data is extracted into anint[]array. This pre-processing is done once at construction so thatfitness(edu.cnu.mdi.sim.ga.triimage.PolygonChromosome)can operate purely on primitive arrays without touching the original image.- Parameters:
target- the image to approximate (non-null; any dimensions)numTriangles- number of triangles per chromosome (must be > 0)- Throws:
NullPointerException- iftargetis nullIllegalArgumentException- ifnumTrianglesis not positive
-
ImageApproximationProblem
public ImageApproximationProblem(BufferedImage target, int numTriangles, ImageFitnessMode fitnessMode) Construct a problem with an explicit fitness metric.- Parameters:
target- target imagenumTriangles- number of triangles per chromosomefitnessMode- error metric to minimize
-
-
Method Details
-
fitness
Compute fitness as the negated configured error between the rendered image and target.The chromosome is rendered into a thread-local
BufferedImageatFITNESS_W×FITNESS_Hpixels. Pixel differences are accumulated as integer squared errors (Fix 3) and normalized to[0, 1]after the loop. The result is negated so that higher fitness corresponds to a closer match.Fitness values lie in
[-1, 0]:0.0— perfect match-1.0— maximally wrong (every channel maximally wrong)
This method is safe to call from multiple threads simultaneously because it uses only
ThreadLocalbuffers, and its one shared mutable field (maxSpanFraction) isvolatilefor safe cross-thread visibility. In the current implementation nothing ever writesmaxSpanFractionafter construction (it stays at its initial unconstrained value of1.0), so it behaves as an effectively immutable configuration value today.- Specified by:
fitnessin interfaceGAProblem<PolygonChromosome>- Parameters:
c- the chromosome to evaluate (non-null)- Returns:
- fitness in
[-1, 0], higher is better
-
getFitnessMode
- Returns:
- configured fitness metric
-
randomIndividual
Generate a random chromosome in which all gene values are drawn independently fromUniform(0, 1).- Specified by:
randomIndividualin interfaceGAProblem<PolygonChromosome>- Parameters:
rng- source of randomness (non-null)- Returns:
- a new randomly initialized
PolygonChromosome
-
initialPopulation
Generate an initial population ofsizerandomly initialized chromosomes.Each individual is produced by
randomIndividual(Random)independently. The returned population is aSimpleGAPopulationbacked by a freshArrayList.- Specified by:
initialPopulationin interfaceGAProblem<PolygonChromosome>- Parameters:
size- number of individuals (must be > 0)rng- source of randomness (non-null)- Returns:
- a new population of
sizerandom individuals
-
getDisplayWidth
public int getDisplayWidth()Return the original target image width in pixels.The view uses this to render chromosomes at display quality via
PolygonChromosome.render(int, int).- Returns:
- display width
-
getDisplayHeight
public int getDisplayHeight()Return the original target image height in pixels.- Returns:
- display height
- See Also:
-
getNumTriangles
public int getNumTriangles()Return the number of triangles per chromosome for this problem.- Returns:
- triangle count
-