/*
* Java Genetic Algorithm Library (@__identifier__@).
* Copyright (c) @__year__@ Franz Wilhelmstötter
*
* 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.
*
* Author:
* Franz Wilhelmstötter (franz.wilhelmstoetter@gmx.at)
*/
package org.jenetics.example.image;
import static java.awt.image.BufferedImage.TYPE_INT_ARGB;
import static java.lang.Math.max;
import static java.lang.Math.round;
import static java.lang.String.format;
import static java.lang.System.getProperty;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Properties;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import javax.imageio.ImageIO;
import org.jenetics.Phenotype;
/**
* Command line version of the Evolving images example.
*/
final class EvolvingImagesCmd {
private static final String DEFAULT_IMAGE =
"org/jenetics/example/image/monalisa.png";
private static final String DEFAULT_OUTPUT_DIR = "evolving-image";
private static final int DEFAULT_GENERATIONS = 10_000;
private static final int DEFAULT_IMAGE_GENERATION = 100;
private static final String PARAM_KEY = "--engine-properties";
private static final String IMAGE_KEY = "--input-image";
private static final String OUTPUT_DIR_KEY = "--output-dir";
private static final String GENERATION_COUNT_KEY = "--generations";
private static final String GENERATION_IMAGE_GAP_KEY = "--image-generation";
private static final String USAGE = "EvolvingImages evolve \n" +
" [--engine-properties <engine.properties>]\n" +
" [--input-image <image.png>]\n" +
" [--output-dir <evolving-images>]\n" +
" [--generations <generation count>]\n" +
" [--image-generation <generation-gap between stored images>]";
private static final String IMAGE_PATTERN = "image-%07d.png";
private EngineParam _engineParam;
private BufferedImage _image;
private File _outputDir;
private int _generations;
private int _imageGeneration;
public EvolvingImagesCmd(final String[] args) {
if (args.length >= 1 && "evolve".equalsIgnoreCase(args[0])) {
final Map<String, String> params = toMap(args);
if (params.containsKey("--help")) {
println(USAGE);
System.exit(0);
}
_engineParam = Optional
.ofNullable(params.get(PARAM_KEY))
.map(this::readEngineParam)
.orElse(EngineParam.DEFAULT);
_image = Optional
.ofNullable(params.get(IMAGE_KEY))
.map(this::readImage)
.orElseGet(this::defaultImage);
_outputDir = Optional
.ofNullable(params.get(OUTPUT_DIR_KEY))
.map(File::new)
.orElse(new File(getProperty("user.dir"), DEFAULT_OUTPUT_DIR));
_generations = Optional
.ofNullable(params.get(GENERATION_COUNT_KEY))
.map(Integer::parseInt)
.orElse(DEFAULT_GENERATIONS);
_imageGeneration = Optional
.ofNullable(params.get(GENERATION_IMAGE_GAP_KEY))
.map(Integer::parseInt)
.orElse(DEFAULT_IMAGE_GENERATION);
}
}
private static Map<String, String> toMap(final String[] args) {
final Map<String, String> props = new HashMap<>();
for (int i = 1; i < args.length; i += 2) {
props.put(args[i], i + 1 < args.length ? args[i + 1] : null);
}
return props;
}
private EngineParam readEngineParam(final String name) {
try (InputStream in = new FileInputStream(name)) {
final Properties props = new Properties();
props.load(in);
return EngineParam.load(props);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
private BufferedImage readImage(final String name) {
try {
return ImageIO.read(new File(name));
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
private BufferedImage defaultImage() {
try (InputStream in = getClass().getClassLoader()
.getResourceAsStream(DEFAULT_IMAGE))
{
return ImageIO.read(in);
} catch (IOException e) {
throw new AssertionError(e);
}
}
public boolean run() {
if (_engineParam != null) {
if (!_outputDir.isDirectory()) {
if (!_outputDir.mkdirs()) {
throw new IllegalArgumentException(
"Can't create output directory " + _outputDir
);
}
}
println("Starting evolution:");
println("* Output dir: " + _outputDir);
println("* Generation count: " + _generations);
println("* Generation image gap: " + _imageGeneration);
println("Engine parameters:");
println("");
println(_engineParam);
println("");
evolve(
_engineParam,
_image,
_outputDir,
_generations,
_imageGeneration
);
}
return _engineParam != null;
}
private static void evolve(
final EngineParam params,
final BufferedImage image,
final File outputDir,
final long generations,
final int generationGap
) {
println("Starting evolution.");
final EvolvingImagesWorker worker = EvolvingImagesWorker.of(params, image);
final AtomicReference<Phenotype<PolygonGene, Double>> latest =
new AtomicReference<>();
final AtomicLong time = new AtomicLong(0);
worker.start((current, best) -> {
final long generation = current.getGeneration();
if (generation%generationGap == 0 || generation == 1) {
final double duration = System.currentTimeMillis() - time.get();
final double speed = generationGap/(duration/1000.0);
time.set(System.currentTimeMillis());
final File file = new File(
outputDir,
format(IMAGE_PATTERN, generation)
);
final Phenotype<PolygonGene, Double> pt = best.getBestPhenotype();
if (latest.get() == null || latest.get().compareTo(pt) < 0) {
log(
"Writing '%s': fitness=%1.4f, speed=%1.2f.",
file, pt.getFitness(), speed
);
latest.set(pt);
final PolygonChromosome ch =
(PolygonChromosome)pt.getGenotype().getChromosome();
writeImage(file, ch, image.getWidth(), image.getHeight());
} else {
log(
"No improvement - %07d: fitness=%1.4f, speed=%1.2f.",
generation, pt.getFitness(), speed
);
}
}
if (generation >= generations) {
worker.stop();
}
});
try {
worker.join();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
static void writeImage(
final File file,
final PolygonChromosome chromosome,
final int width,
final int height
) {
final double MIN_SIZE = 500;
final double scale = max(max(MIN_SIZE/width, MIN_SIZE/height), 1.0);
final int w = (int)round(scale*width);
final int h = (int)round(scale*height);
try {
final BufferedImage image = new BufferedImage(w, h, TYPE_INT_ARGB);
final Graphics2D graphics = image.createGraphics();
chromosome.draw(graphics, w, h);
ImageIO.write(image, "png", file);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
private static void println(final Object format, final Object... args) {
System.out.printf(Objects.toString(format) + "\n", args);
}
private static void log(final Object pattern, final Object... args) {
final LocalDateTime now = LocalDateTime.now();
final String tss = now.toLocalDate().toString() + ' ' +
now.toLocalTime().toString();
final String p = format("%s - ", tss) + pattern;
System.out.println(format(p, args));
}
}