27 de out. de 2017

JavaFX com Bean Validation e CDI 2.0

Quando JavaFX 2 foi lançado me lembro de muitas pessoas procurando por frameworks que facilitariam a integração de JavaFX com frameworks de validação, um container de injeção de independência e outros serviços enteprise. Atualmente pode integrar JavaFX com Bean Validation e CDI 2.0 sem o uso de qualquer framework externo. Nesse artigo eu vou mostrar como validar os campos de sua aplicação JavaFX usando Bean Validation  e uma aplicação com CDI.

Antes de falar sobre essas coisas eu recomendo que você cheque os seguintes artigos:


Validando Controls do JavaFX usando Bean Validation



Com Bean Validation 2.0 temos o "unwrap" automático de propriedades JavaFX. Isso significa que se você tiver um campo do tipo ObservableValue o validator vai conseguir tirar o valor real e aplicar a validação. No entanto, os campos de control do JavaFX ainda não são suportados, então se você tentar validar os campos diretamente você vai ter uma exceção:  


Isso acontece por uqe a implementação do validator não sabe como retirar valores de um controller JavaFX. O que precisamos é criar uma classe que implementa javax.validation.valueextraction.ValueExtractor. Precisamos também usar o tipo do controle que essa classe vai saber tirar o valor (usar genéricos para isso), criar o método que faz a atual extração do valor (tipo um getText em um TextField) e, por fim, usar a anotação  javax.validation.valueextraction.ExtractedValue para configurar o tipo que é tirado do control(String, Integer, LocalDate...). Para evitar passos adicionais usamos a anotação  javax.validation.valueextraction.UnwrapByDefault na nossa implementação. As nossas classes para tirar valores do DatePicker e do TextField estão abaixo:

Value Extractor para o DatePicker

Value Extractor para o TextField

Finalmente, precisamos registrar isso com o framework de validação. Há 
Finally we must register it in within the bean validation framework. There are a couple of algumas formas de fazer isso, escolhemos o modo com SPI  que é criar um arquivo com o nome META-INF/services/javax.validation.valueextraction.ValueExtractor com o nome das classes dos nossos ValueExtractors:

Conteúdo do arquivo META-INF/services/javax.validation.valueextraction.ValueExtractor


Poderíamos ter uma extensão de bean validation para os controles do JavaFX, assim não teríamos que criar novamente. Se você sabe de algum projeto assim me fale que eu menciono aqui!

Mostrar os erros de validation para os usuários


Você provavelmente quer mostrar os erros para os usuários quando os valores que ele entrou não são válidos. Uma vez que você tenha acesso a classe Validator no controller você simplesmente pode chamar o método validate no controller  e mostrar aos usuários os erros de validação em um label, por exemplo. Eu pessoalmente não gosto dessa solução pois eu acho que é muito intrusivo já que você terá que modificar a view para incluir mais labels só para a validação. Uma solução mais simples é mostrar um tooltip com o erro de validação no campo que deu problema. Poderíamos também mostrar um dialog ou bordar o campo de vermelho, mas só mostramos o tooltip mesmo:

Método showValidationErrors pega o controle que tem erro de validação adiciona um tooltip e mostra

Para mostrar o tooltip precisamos acessar o controle em sí e para isso precisamos fazer um pouco de reflexão por isso fomos ao monte meditar , por isso o campo usado deve ter o modificador público ou ter métodos de acesso a ele.

CDI, Bean Validation e JavaFX


Não há nada para adicionar sobre CDI aqui do que eu já mencionei no post Using CDI 2.0 in a JavaFX application. Eu gostaria de compartilhar que CDI e Bean Validation funcionam em uma aplicação JavaFX e por isso eu posso simplesmente injetar o validator na minha classe!Uma única dependência vai tornar isso possível: org.hibernate.validator:hibernate-validator-cdi. Veja abaixo todos os arquivos da aplicação:

Our maven project and its files


Essas são as dependências que eu adicionei ao pom.xml:

These are all dependencies required to use bean validation and CDI on a JavaFX application

O código de uma aplicação de exemplo pode ser encontrado no meu github github. Abaixo você pode ver como a validação usando tooltips funciona na aplicação:



Por quey isso é importante?


Estar apto a integrar JavaFX com JavaEE (ou devo dizer EE4J já?) é algo chave para quem quer criar aplicação reais com JavaFX, isso trás persistência, validação, injeção de dependência e mais! it will bring persistence, validation, injection and more. A principal questão é: será que isso funcionaria em uma aplicação Android que usa Gluon/ JavaFX Ports?

Para mais exemplos de validação você pode ver essa pull request do Hendrik Ebbers  para o projeto hibernate validator.

25 de out. de 2017

Usando CDI 2.0 em uma aplicação JavaFX

Com o lançamento do CDI 2.0 nós temos um container que pode ser usado em uma aplicação Java SE! Anteriorment se você quisesse fazer uso de CDI em uma aplicação Java SE você teria que usar classes proprietárias do Weld, veja o artigo FXML & JavaFX—Fueled by CDI & JBoss Weld.

Essa feature é bem explicada pelo Adam Bien em um vídeo, veja:


Claro que era meu objetivo tentar isso em uma aplicação javaFX e nesse artigo breve compartilharei com vocês a minha experiência e o código.

Crie o container em uma aplicação JavaFX


Uma aplicação JavaFX é uma classe que estende de javafx.application.Application, que é o ponto de entrada para aplicações JavaFX e onde o stage principal pode ser usado. Para usar CDI devemos criar o SeContainer e certificar-se que ele irá criar todas as classes gerenciadas pelo CDI. Isso deve ser feito no ponto de entrada da Application para que possamos gerenciar todas as classes criadas de lá.

Uma vez criado o container você tem diferente formas de enviar o Stage para a aplicação JavaFX (a que você vai programar e não a que criamos para grudar CDI ao javaFX). Por exemplo, você pode fazer com que users possam estender uma interface e então injetar a classe do usuário dentro da nossa CDI application para invocar por lá.



Hpa uma forma mais elegante que é usando a API de observer do CDI. Nesse caso podemos criar uma anotação que será o qualifier para o evento  javafx.stage.Stage. CDI sabe inteligentemente as classes que observam aquele evento e vai selecionar a classe quando ativarmos o evento com o stage principal. Isso é semelhante ao que você pode ver no artigo que mencionamos FXML & JavaFX—Fueled by CDI & JBoss Weld.



Independente da solução que escolher não "desligue o container" ou isso pode deixar app inconsistente. Na verdade isso não deve afetar muito, mas eu decidi não fazer o shutdown.

Beleza! Daqui pra frente você pode usar CDI na sua app! Vá em frente e injete coisas nas suas classes JavaFX. Exceto, é claro, se você estiver usando FXML. Nesse caso JavaFX cria os controllers para você usando reflection e CDI não estará ciente da classe criada, ou seja, vai tudo ser nulo pois CDI não injetará. Uma solução é criar um FXMLLoader e fazer o CDI criar os controllers invés de deixar o FXML loader criar ele. O código abaixo foi novamente pego de  FXML & JavaFX—Fueled by CDI & JBoss Weld mas com a adição do delicioso lambda do Java 8:






Finalmente você pode criar sua própria aplicação mas NUNCA chame os métodos estáticos do FXMLLoader, use o injetado, por exemplo:



Lembre que você agora pode injetar coisas no seu controller. No nosso caso eu injetei um clássico Greeter, semelhante ao que abordo no post Primeiros Passos com CDI:





Não se esqueça também  do arquivo vazio beans.xml que irá ativar CDI. Essa é a estrutura do projeto que eu usei nos meus testes:




O resultado final foi mostrado abaixo - a diferença é que a mensagem veio de um bean gerenciado por FXML:


O código completo pode ser encontrado no github.

19 de jun. de 2017

Algoritmo k-means e árvores de decisão com Weka e JavaFX

Weka é uma das ferramentas mais conhecidas para Machine Learning em Java, que também tem uma ótima API Java que inclui APIs para agrupamento (ou clustering) de dados usando o algoritmo k-means. Usando JavaFX é possível visualizar dados não-classificados e classsificar os dados usando as APIs usando APIs Weka e então visualizar o resultado em um gráfico JavaFX como o gráfico "scatter".


Nesse post vamos mostrar como  uma simples aplicação JavaFX permite você carregar dados, mostrar os dados sem distinção de categoria usando um gráfico, então usaremos weka para classificar os dados usando k-means e finalmnte vamos classificar os dados usando uma árvore de decisão. Usaremos os dados do arquivo iris.2D.arff que vem junto com o download do Weka

K-means clustering usando Weka é realmente simples e requer somente algumas linhas de código como você pode ver nesse post. Na nossa applicação iremos construir 3 gráficos para os dados de flores íris:

  1. Dados sem distinção de classe (sem séries)
  2. Os dados com a classificação real
  3. Dados clusterizados usando Weka

Como você pode ver os dados foram agrupados de uma forma que é bem próxima dos dados reais (os dados com valores coletados na vida real). O código para construir os dados agrupados é:

private List<Series<Number, Number>> buildClusteredSeries(Instances data) throws Exception {
List<XYChart.Series<Number, Number>> clusteredSeries = new ArrayList<>();
// to buld the cluster we remove the class information
Remove remove = new Remove();
remove.setAttributeIndices("3");
remove.setInputFormat(data);
Instances dataToBeClustered = Filter.useFilter(data, remove);
SimpleKMeans kmeans = new SimpleKMeans();
kmeans.setSeed(10);
kmeans.setPreserveInstancesOrder(true);
kmeans.setNumClusters(3);
kmeans.buildClusterer(dataToBeClustered);
data.deleteStringAttributes();
int[] assignments = kmeans.getAssignments();
for (int c = 0; c < 3; c++) {
XYChart.Series<Number, Number> series = new XYChart.Series<>();
series.setName("Cluster " + c);
clusteredSeries.add(series);
}
for (int i = 0; i < assignments.length; i++) {
int clusterNum = assignments[i];
clusteredSeries.get(clusterNum).getData().add(instancetoChartData(data.get(i)));
}
return clusteredSeries;
}

Depois de montar esses três gráficos também modifiquei todo o código para adicionar um classificar com árvores de decisão usando a implementação do  algoritmo J48. Logo após os gráficos você pode ver a árvore de decisão que montamos a partir dos dados:



Quando você clica no gráfico sem classificação você verá que novos dados são adicionados e ele será classificado nos gráficos superiores usando a árvore de decisão e o algoritmo k-means de clustering.

Nós usamos a árvore de decisão que geramos para classificar os ados e também o cluster. Na imagem acima o cluster classifica alguns dados de forma diferente do que é classificado com a árvore de decisão.

datafile = new BufferedReader(new FileReader(DATA_SET));
data = new Instances(datafile);
data.setClassIndex(data.numAttributes() - 1);
tree = new J48();
tree.buildClassifier(data);
Instance instance = new DenseInstance(3);
instance.setDataset(data);
instance.setValue(0, xValue.doubleValue());
instance.setValue(1, yValue.doubleValue());
double predictedClass = tree.classifyInstance(instance);
instance.setValue(2, pred

Eu acho que é particularmente interessante como é fácil visualizar dados com JavaFX. O código completo para esse projeto pode ser encontrado no meu  github, mas aqui está o código da classe principal:



package org.fxapps.ml;
import java.io.BufferedReader;
import java.io.FileReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import javafx.application.Application;
import javafx.geometry.Point2D;
import javafx.scene.Scene;
import javafx.scene.chart.Axis;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.ScatterChart;
import javafx.scene.chart.XYChart;
import javafx.scene.chart.XYChart.Data;
import javafx.scene.chart.XYChart.Series;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.Separator;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.text.Font;
import javafx.scene.text.FontPosture;
import javafx.scene.text.FontWeight;
import javafx.scene.text.Text;
import javafx.scene.text.TextAlignment;
import javafx.stage.Stage;
import weka.classifiers.trees.J48;
import weka.clusterers.SimpleKMeans;
import weka.core.Attribute;
import weka.core.DenseInstance;
import weka.core.Instance;
import weka.core.Instances;
import weka.filters.Filter;
import weka.filters.unsupervised.attribute.Remove;
public class Clustering extends Application {
private static final int NUMBER_OF_CLASSES = 3;
private static final String DATA_SET = "/opt/weka/weka-3-7-12/data/iris.2D.arff";
private ScatterChart<Number, Number> clusteredChart;
private ScatterChart<Number, Number> realDataChart;
private ScatterChart<Number, Number> noClassificationChart;
private static int swapIndex = 0;
private int[][] swapColorsCombinations = { { 0, 1 }, { 0, 2 }, { 1, 2 } };
private J48 tree;
private Instances data;
public static void main(String[] args) throws Exception {
launch();
}
@Override
public void start(Stage stage) throws Exception {
loadData();
tree = new J48();
tree.buildClassifier(data);
noClassificationChart = buildChart("No Classification (click to add new data)", buildSingleSeries());
clusteredChart = buildChart("Clustered", buildClusteredSeries());
realDataChart = buildChart("Real Data (+ Decision Tree classification for new data)", buildLabeledSeries());
noClassificationChart.setOnMouseClicked(e -> {
Axis<Number> xAxis = noClassificationChart.getXAxis();
Axis<Number> yAxis = noClassificationChart.getYAxis();
Point2D mouseSceneCoords = new Point2D(e.getSceneX(), e.getSceneY());
double x = xAxis.sceneToLocal(mouseSceneCoords).getX();
double y = yAxis.sceneToLocal(mouseSceneCoords).getY();
Number xValue = xAxis.getValueForDisplay(x);
Number yValue = yAxis.getValueForDisplay(y);
reloadSeries(xValue, yValue);
});
Label lblDecisionTreeTitle = new Label("Decision Tree generated for the Iris dataset:");
Text txtTree = new Text(tree.toString());
Button btnRestore = new Button("Restore original data");
Button btnSwapColors = new Button("Swap clustered chart colors");
VBox vbDecisionTree = new VBox(10, lblDecisionTreeTitle, new Separator(), txtTree, btnRestore, btnSwapColors);
btnRestore.setOnAction(e -> {
loadData();
reloadSeries();
});
btnSwapColors.setOnAction(e -> swapClusteredChartSeriesColors());
lblDecisionTreeTitle.setTextFill(Color.DARKRED);
lblDecisionTreeTitle.setFont(Font.font(Font.getDefault().getFamily(), FontWeight.BOLD, FontPosture.ITALIC, 16));
txtTree.setTranslateX(100);
txtTree.setFont(Font.font(Font.getDefault().getFamily(), FontWeight.BOLD, FontPosture.ITALIC, 14));
txtTree.setLineSpacing(1);
txtTree.setTextAlignment(TextAlignment.LEFT);
vbDecisionTree.setTranslateY(20);
vbDecisionTree.setTranslateX(20);
GridPane gpRoot = new GridPane();
gpRoot.add(realDataChart, 0, 0);
gpRoot.add(clusteredChart, 1, 0);
gpRoot.add(noClassificationChart, 0, 1);
gpRoot.add(vbDecisionTree, 1, 1);
stage.setScene(new Scene(gpRoot));
stage.setTitle("Íris dataset clustering and visualization");
stage.show();
}
private void loadData() {
BufferedReader datafile;
try {
datafile = new BufferedReader(new FileReader(DATA_SET));
data = new Instances(datafile);
data.setClassIndex(data.numAttributes() - 1);
} catch (Exception e) {
System.out.println("Exception loading data... Leaving");
e.printStackTrace();
System.exit(0);
}
}
private void reloadSeries(Number xValue, Number yValue) {
try {
Instance instance = new DenseInstance(NUMBER_OF_CLASSES);
instance.setDataset(data);
instance.setValue(0, xValue.doubleValue());
instance.setValue(1, yValue.doubleValue());
double predictedClass = tree.classifyInstance(instance);
instance.setValue(2, predictedClass);
data.add(instance);
reloadSeries();
} catch (Exception e) {
e.printStackTrace();
}
}
private void reloadSeries() {
try {
noClassificationChart.getData().clear();
clusteredChart.getData().clear();
realDataChart.getData().clear();
noClassificationChart.getData().addAll(buildSingleSeries());
clusteredChart.getData().addAll(buildClusteredSeries());
realDataChart.getData().addAll(buildLabeledSeries());
} catch (Exception e) {
e.printStackTrace();
}
}
private void swapClusteredChartSeriesColors() {
List<Series<Number, Number>> clusteredSeries = new ArrayList<>();
// we have to copy the original data to swap the series
clusteredChart.getData().forEach(serie -> {
Series<Number, Number> series = new Series<>();
serie.getData().stream().map(d -> new Data<Number, Number>(d.getXValue(), d.getYValue()))
.forEach(series.getData()::add);
clusteredSeries.add(series);
});
int i = swapColorsCombinations[swapIndex][0];
int j = swapColorsCombinations[swapIndex][1];
Collections.swap(clusteredSeries, i, j);
clusteredChart.getData().clear();
clusteredChart.getData().addAll(clusteredSeries);
swapIndex = swapIndex == NUMBER_OF_CLASSES - 1 ? 0 : swapIndex + 1;
}
private List<XYChart.Series<Number, Number>> buildSingleSeries() {
XYChart.Series<Number, Number> singleSeries = new XYChart.Series<>();
data.stream().map(this::instancetoChartData).forEach(singleSeries.getData()::add);
singleSeries.setName("no classification");
return Arrays.asList(singleSeries);
}
private List<Series<Number, Number>> buildLabeledSeries() {
List<XYChart.Series<Number, Number>> realSeries = new ArrayList<>();
Attribute irisClasses = data.attribute(2);
data.stream().collect(Collectors.groupingBy(d -> {
int i = (int) d.value(2);
return irisClasses.value(i);
})).forEach((e, instances) -> {
XYChart.Series<Number, Number> series = new XYChart.Series<>();
series.setName(e);
instances.stream().map(this::instancetoChartData).forEach(series.getData()::add);
realSeries.add(series);
});
return realSeries;
}
private List<Series<Number, Number>> buildClusteredSeries() throws Exception {
List<XYChart.Series<Number, Number>> clusteredSeries = new ArrayList<>();
// to build the cluster we remove the class information
Remove remove = new Remove();
remove.setAttributeIndices("3");
remove.setInputFormat(data);
Instances dataToBeClustered = Filter.useFilter(data, remove);
SimpleKMeans kmeans = new SimpleKMeans();
kmeans.setSeed(10);
kmeans.setPreserveInstancesOrder(true);
kmeans.setNumClusters(3);
kmeans.buildClusterer(dataToBeClustered);
IntStream.range(0, 3).mapToObj(i -> {
Series<Number, Number> newSeries = new XYChart.Series<>();
newSeries.setName(String.valueOf(i));
return newSeries;
}).forEach(clusteredSeries::add);
int[] assignments = kmeans.getAssignments();
for (int i = 0; i < assignments.length; i++) {
int clusterNum = assignments[i];
clusteredSeries.get(clusterNum).getData().add(instancetoChartData(data.get(i)));
}
return clusteredSeries;
}
private XYChart.Data<Number, Number> instancetoChartData(Instance i) {
return new XYChart.Data<Number, Number>(i.value(0), i.value(1));
}
private ScatterChart<Number, Number> buildChart(String chartName, List<XYChart.Series<Number, Number>> series) {
final NumberAxis xAxis = new NumberAxis();
final NumberAxis yAxis = new NumberAxis();
final ScatterChart<Number, Number> sc = new ScatterChart<Number, Number>(xAxis, yAxis);
sc.setTitle(chartName);
sc.setPrefHeight(450);
sc.setPrefWidth(600);
xAxis.getValueForDisplay(1);
yAxis.getValueForDisplay(2);
sc.getData().addAll(series);
return sc;
}
}
view raw Clustering.java hosted with ❤ by GitHub

13 de jun. de 2017

Reconhecendo dígitos manuscritos em uma aplicação JavaFX usando DeepLearning4J





Já falamos sobre tensorflow e JavaFX no blog em inglês, mas a API Java do tensorflow ainda está incompleta. Uma API madura e melhor documentada é o DeepLearning4J.


Nesse exemplo nos carregamos o modelo treinado na nossa aplicação, criamos um canvas para desenhar e quando o Enter é pressionado, a imagem do canvas é redimensionada e enviada para o modelo já treinado do deeplearning4j para reconhecimento:






A forma que isso "advinha digitos é como o "olá mundo" para aprendizado usando redes neurais. Um neurônio imita o neurônio natural do nosso cérebro e ele tem um "peso" que controla quando o neurônio é ativado (no nosso cérebro acontece químicas para ativar um neurônio).Uma rede neural consiste de muitos neurônios conectados uns aos outros e também organizados em camadas. O que temos que fazer é fornecer dados identificados (com labels) para a nossa rede neural e ajustar os pesos dos neurônio até que ela possa "advinhar" resultados para um valor novo que não sabemos. Esse processo é chamado de treinamento.




Uma vez treinada, nós testamos a rede neural com outros valores também conhecidos para  saber a precisão da rede neural (no nosso caso a precisão é 97.5%!). No nosso case usamos o famoso conjunto de dados  MNIST.


Pelo motivo de termos camadas ocultas entra a camada de entrada de dados e a camada de saída,  nós chamados essas redes neurais de "profundas" (deep neural network) e são usadas no processo de aprendizagem profunda. Nós temos muitos outros conceitos e tipos de redes neurais, eu sugiro você assistis alguns vídeos super interessantes sobre isso no youtube.



E se você estiver ouvindo falar disso pela primeira vez tenha em mente que não será a última!

Se você tentar o código abaixo vai ver que não é tão preciso quanto essa app web, por exemplo. O motivo é que eu não manipulo a imagem precisamente antes de enviar ela para predição, simplesmente redimensionamos ela para ter  28x28 pixels como requerido pelo modelo treinado..

O código da aplicação JavaFX é mostrado abaixo e o projeto completo está no meu github, including o código usando para treinar nossa rede neural que foi tirado dos deeplearning4j examples.

package org.fxapps.deeplearning;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import org.datavec.image.loader.NativeImageLoader;
import org.deeplearning4j.nn.multilayer.MultiLayerNetwork;
import org.deeplearning4j.util.ModelSerializer;
import org.nd4j.linalg.api.ndarray.INDArray;
import org.nd4j.linalg.dataset.api.preprocessor.ImagePreProcessingScaler;
import javafx.application.Application;
import javafx.embed.swing.SwingFXUtils;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.Label;
import javafx.scene.image.ImageView;
import javafx.scene.image.WritableImage;
import javafx.scene.input.KeyCode;
import javafx.scene.input.MouseButton;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.StrokeLineCap;
import javafx.stage.Stage;
public class MnistTestFXApp extends Application {
private final int CANVAS_WIDTH = 150;
private final int CANVAS_HEIGHT = 150;
private NativeImageLoader loader;
private MultiLayerNetwork model;
private Label lblResult;
public static void main(String[] args) throws IOException {
launch();
}
@Override
public void start(Stage stage) throws Exception {
Canvas canvas = new Canvas(CANVAS_WIDTH, CANVAS_HEIGHT);
ImageView imgView = new ImageView();
GraphicsContext ctx = canvas.getGraphicsContext2D();
model = ModelSerializer.restoreMultiLayerNetwork(new File("minist-model.zip"));
loader = new NativeImageLoader(28,28,1,true);
imgView.setFitHeight(100);
imgView.setFitWidth(100);
ctx.setLineWidth(10);
ctx.setLineCap(StrokeLineCap.SQUARE);
lblResult = new Label();
HBox hbBottom = new HBox(10, imgView, lblResult);
VBox root = new VBox(5, canvas, hbBottom);
hbBottom.setAlignment(Pos.CENTER);
root.setAlignment(Pos.CENTER);
Scene scene = new Scene(root, 520, 300);
stage.setScene(scene);
stage.show();
stage.setTitle("Handwritten digits recognition");
canvas.setOnMousePressed(e -> {
ctx.setStroke(Color.WHITE);
ctx.beginPath();
ctx.moveTo(e.getX(), e.getY());
ctx.stroke();
});
canvas.setOnMouseDragged(e -> {
ctx.setStroke(Color.WHITE);
ctx.lineTo(e.getX(), e.getY());
ctx.stroke();
});
canvas.setOnMouseClicked(e -> {
if (e.getButton() == MouseButton.SECONDARY) {
clear(ctx);
}
});
canvas.setOnKeyReleased(e -> {
if(e.getCode() == KeyCode.ENTER) {
BufferedImage scaledImg = getScaledImage(canvas);
imgView.setImage(SwingFXUtils.toFXImage(scaledImg, null));
try {
predictImage(scaledImg);
} catch (Exception e1) {
e1.printStackTrace();
}
}
});
clear(ctx);
canvas.requestFocus();
}
private BufferedImage getScaledImage(Canvas canvas) {
// for a better recognition we should improve this part of how we retrieve the image from the canvas
WritableImage writableImage = new WritableImage(CANVAS_WIDTH, CANVAS_HEIGHT);
canvas.snapshot(null, writableImage);
Image tmp = SwingFXUtils.fromFXImage(writableImage, null).getScaledInstance(28, 28, Image.SCALE_SMOOTH);
BufferedImage scaledImg = new BufferedImage(28, 28, BufferedImage.TYPE_BYTE_GRAY);
Graphics graphics = scaledImg.getGraphics();
graphics.drawImage(tmp, 0, 0, null);
graphics.dispose();
return scaledImg;
}
private void clear(GraphicsContext ctx) {
ctx.setFill(Color.BLACK);
ctx.fillRect(0, 0, 300, 300);
}
private void predictImage(BufferedImage img ) throws IOException {
ImagePreProcessingScaler imagePreProcessingScaler = new ImagePreProcessingScaler(0, 1);
INDArray image = loader.asRowVector(img);
imagePreProcessingScaler.transform(image);
INDArray output = model.output(image);
String putStr = output.toString();
lblResult.setText("Prediction: " + model.predict(image)[0] + "\n " + putStr);
}
}

24 de mai. de 2017

Controlando o Arduino de uma aplicação JavaFX

É muito empolgante isso! Eu tinha acabado de escrever um pequeno artigo sobre Java e JArduino, mas eu queria fazer um teste com JavaFX e em poucos minutos estava funcionando!

Mostrando informação de um sensor de luz em label


No post sobre Java e JArduino nós imprimimos no console a atual intensidade de luz vinda de um sensor LDR conectado a um arduino. Em menos de um minuto conseguimos mostrar essa informação em um label, veja:



 Eu basicamente reusei a mesma classe do outro post e li a saída em uma thread JavaFX (veja Platform.runLater no código abaixo).

package org.fxapps.arduino;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class App extends Application {
LightSensorApp lightSensorApp;
public static void main(String[] args) { // this method should not be required on fx apps
launch();
}
@Override
public void start(Stage stage) throws Exception {
Label lbl = new Label();
Scene scene = new Scene(new StackPane(lbl),300, 100);
lightSensorApp = new LightSensorApp("/dev/ttyUSB0" , s ->
Platform.runLater(() -> lbl.setText("light: " + s ))
);
lightSensorApp.runArduinoProcess();
stage.setScene(scene);
stage.setTitle("Light control with monitoring");
stage.show();
}
}
view raw App.java hosted with ❤ by GitHub
O plano era também ligar um led da aplicação JavaFX, então vamos seguir o plano original.

Controlando um LED e lendo o LDR


Na segunda versão eu usei um gráfico para mostrar os dados do sensor LDR em tempo relam e também um botão para controlar um LED, então quanod ligamos o LED podemos ver o valor do sensor mudando.

Veja nosso circuito e note um LED no pino digital D1:



Agora o código da classe LightSensorApp (que estende de  JArduino) foi modificado para incluir um comando de um LED. A App JavaFX ainda é simples, tem um gráfico e um botão:



O seguinte vídeo mostra a aplicação em ação:



O código está mostrado abaixo e não modificamos o pom.xml, somente dois arquivos Java que temos em nosso projeto.

package org.fxapps.arduino;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javafx.scene.chart.XYChart.Data;
import javafx.scene.control.ToggleButton;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class App extends Application {
XYChart.Series<Number, Number> series;
LightSensorApp lightSensorApp;
long initialTime;
public static void main(String[] args) {
launch();
}
@Override
public void start(Stage stage) throws Exception {
lightSensorApp = new LightSensorApp("/dev/ttyUSB0", false, this::updateChart);
LineChart<Number, Number> chart = createChart();
initialTime = System.currentTimeMillis();
series = new XYChart.Series<>();
ToggleButton tbLed = new ToggleButton("LED");
tbLed.selectedProperty().addListener(c -> lightSensorApp.setTurnOnLed(tbLed.isSelected()));
chart.getData().add(series);
lightSensorApp.runArduinoProcess();
VBox vb = new VBox(50, chart, tbLed);
vb.setAlignment(Pos.CENTER);
Scene scene = new Scene(vb, 800, 600);
stage.setScene(scene);
stage.setTitle("Light control with monitoring");
stage.show();
stage.setOnCloseRequest(e -> System.exit(0));
}
private LineChart<Number, Number> createChart() {
LineChart<Number, Number> chart = new LineChart<>(new NumberAxis(), new NumberAxis());
chart.setTitle("Light Intensity");
chart.getXAxis().setTickLabelsVisible(false);
chart.getXAxis().setLabel("Time");
chart.getXAxis().setTickMarkVisible(false);
chart.setCreateSymbols(false);
chart.setLegendVisible(false);
chart.setAlternativeColumnFillVisible(false);
return chart;
}
private void updateChart(Short s) {
long time = System.currentTimeMillis() - initialTime;
Data<Number, Number> data = new XYChart.Data<>(time, s);
Platform.runLater(() -> series.getData().add(data));
}
}
view raw App.java hosted with ❤ by GitHub
package org.fxapps.arduino;
import java.util.function.Consumer;
import org.sintef.jarduino.AnalogPin;
import org.sintef.jarduino.DigitalPin;
import org.sintef.jarduino.InvalidPinTypeException;
import org.sintef.jarduino.JArduino;
public class LightSensorApp extends JArduino {
private final DigitalPin LED_PIN = DigitalPin.PIN_2;
private Consumer<Short> receiveLight;
private boolean turnOnLed;
public LightSensorApp(String port, boolean led, Consumer<Short> receiveLight) {
super(port);
this.turnOnLed = led;
this.receiveLight = receiveLight;
}
@Override
protected void setup() throws InvalidPinTypeException {
pinMode(LED_PIN, OUTPUT);
}
@Override
protected void loop() throws InvalidPinTypeException {
short light = analogRead(AnalogPin.A_0);
digitalWrite(LED_PIN, turnOnLed? HIGH : LOW);
System.out.println(light);
receiveLight.accept((short) map(light, 300, 1023, 0, 100));
delay(100);
}
public void setTurnOnLed(boolean turnOnLed) {
this.turnOnLed = turnOnLed;
}
}

O código usado nesse projeto está no  meu github.




18 de jan. de 2017

Física Dinâmica básica em uma aplicação JavaFX

Sabe o que é bacana? Programar para se divertir. Algumas das melhores formas de se fazer isso é criando jogos, como o Pong, ou brincando com a natureza, como mostra o livro Nature Of Code.
Nessa postagem iremos mostrar os primeiros capítulos desse livro aplicados em um mundo JavaFX. O que iremos mostar:

  • Gravidade
  • Aplicando outras forças

Claro que o mundo da física e da matemática são muito maiores do que isso, mas espero que a atual aplicação inspire o leitor a criar as suas próprias coisas.


Sobre a app de teste


o modelo de nossa aplicação é o já tão conhecido Canvas com uma engine: O Canvas é uma tela de desenho, para desenharmos nele continuamente usamos uma Timeline, o famoso loop.

As coisas desenhadas em um canvas precisam de um X e Y e dentro do loop atualizamos o x e o y de acordo com os acontecimentos e com a entrada do usuário. Para mantermos referëncias aos valores de X e Y, usamos uma classe do JavaFX chamada javafx.geometry.Point2D.

Tendo isso em mente, vamos entender alguns conceitos usados na nossa aplicação.

Conceitos


Vetor: Um vetor na nossa aplicação é uma posicação X e Y, sendo que a direção do vetor dependerá dos sinais X e Y. Na matemática o vetor é um pouco diferente, mas as ideias são semelhantes 
Velocidade: É a variação da posição de um corpo no espaço em um dado período. No nosso código a velocidade se aplica continuamente a posição do corpo na execução do loop da aplicação;
Aceleração: É a variação da velocidade com o passar do tempo, que, no nosso caso, é determinado também pelo loop;
Massa: É a quantidade de matéria que tem um corpo.

Pois bem, na nossa aplicação o corpo é definido por uma bolinha. Aplicando os conceitos acima, o que temos é uma bolinha parada no meio da tela. Veja:


Muito sem graça, né? Nada acontece, feijoada, com essa aplicação. O motivo é só um, não há forças aplicadas sobre o corpo. Mas o que é uma força. De acordo com Isaac Newton, força é qualquer agente externo que modifica o movimento de um corpo livre ou causa deformação num corpo fixo.  Não, não estamos falando da força dos Jedi...
Na verdade, uma força está sendo aplicada sobre o seu corpo nesse exato momento: a Gravidade. A gravidade te puxa para o centro da terra e é por isso que estamos sempre no chão. A nossa bolinha ali em cima está parada, não tem gravidade no mundo dos pixels. E com essa aplicação sem graça, nós já podemos entender a primeira lei de Newton: um corpo em repouso permanece em repouso e um corpo em movimento permanece em movimento a menos que sofra efeito de alguma força. Ou seja, essa bolinha está parada aí e é aí que ela vai ficar, exceto se tiver alguma força que faça ela se mover!
Podemos agora modificar nosso programa para mudar isso, podemos aplicar um vetor de gravidade na bolinha.  Importante dizer que limitamos a bolinha as bordas da aplicação, assim, toda vez que a bolinha chega nas bordas da tela, simplesmente invertemos a direção do vetor de velocidade.

O vetor de gravidade "puxa pra baixo", logo, ele precisa ter um X=0 e um Y positivo, vamos colocar 0.1. O resultado é uma bolinha feliz pulando:



Que bacana, né? Agora vamos colocar outra força: o vento. O vento é uma força simples de se colocar e  vamos dizer que o vento bata da esquerda para a direita, precisamos de um vetor que tenha essa direção(X=algum número positivo, Y=0). O resultado é como se segue:


Note que a bolinha agora fica indo para o canto, bate na "parede" e quando vai voltar, o vento não a deixa!

Notem que até agora não falamos da massa. Pois bem, a bolinha tem uma massa e de acordo com essa massa, a força é modificada, afinal, se bate um vento no seu quarto algumas coisas leves (um copo de plástico, por exemplo) pode voar, mas não o seu computador e nem você e isso acontece por que as coisas têm massa diferente. Para visualizar isso, colocamos mais 5 bolinhas na tela com massas diferentes. Veja o resultado:



A aplicação de forças deve respeitar a segunda lei de Newton que definiu a fórmula que diz que força é igual massa vezes aceleração. Ao aplicarmos a força no código, dividimos ela pela massa e adicionamos à aceleração.

No vídeo abaixo você vai notar que a velocidade das bolinhas é diferente e isso acontece por causa da massa delas. Como o vento é constante, tudo acaba se movendo, mas isso muda quando adicionamos a força de fricção (atrito, ótima explicação pode ser encontrada nesse vídeo). A fricção faz com que haja a perda de movimento de acordo com o tempo, logo, o que acontece uns segundos após rodar a aplicação com o adicional da fricção é o seguinte:



Vou parar por aqui pois o livro mencionado no início do post tem quase 500 páginas e daria pra continuar aplicando os conceitos em JavaFX até o livro terminar. Veja um pequeno vídeo da aplicação mostrada acima


Conclusão


Falamos um pouco sobre física e também programamos. Quem sabe um dia as aulas nas escolas brasileiras não serão assim? Minha missão foi concluída se você está lendo essa linha empolgado e até considerando fazer seus testes localmente. Claro que o que esboçamos é um exemplo muito simples e trata os conceitos de forma muito superficial, mas, mesmo assim, é muito interessante ver isso em ação.

O código total da aplicação pode ser encontrado nosso github.


16 de jan. de 2017

O jogo "Pong" em JavaFX usando 90 linhas de código



Nesse post eu vou compartilhar uma versão do jogo Pong com vocês que usa somente 90 linhas de código.

Para executar o jogo, baixe o código abaixo e salve em um arquivo Pong.java e, considerando que você tenha Java 8, rode:

 javac Pong.java

Compila o código Java

java Pong

Roda o jogo.

O código funciona com o play 1 (o retângulo do lado esquerdo) seguindo o mouse e o outro tentando se posicionar de acordo com a bolinha. Desenhamos o jogo em um canvas e usamos uma timeline como "engine". Quando a bolinha ultrassa os limites laterais da tela, resetamos o jogo e damos o ponto para quem está do outro lado. Quando a bolinha ultrapassa os limites, mas extamente na posição de um dos players, trocamos a sua direção - mesma coisa quando a bolinha ultrapassa o topo ou a parte de baixo.

Veja o código!


import java.util.Random;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.text.Font;
import javafx.scene.text.TextAlignment;
import javafx.stage.Stage;
import javafx.util.Duration;
public class Pong extends Application {
private static final int width = 800;
private static final int height = 600;
private static final int PLAYER_HEIGHT = 100;
private static final int PLAYER_WIDTH = 15;
private static final double BALL_R = 15;
private int ballYSpeed = 1;
private int ballXSpeed = 1;
private double playerOneYPos = height / 2;
private double playerTwoYPos = height / 2;
private double ballXPos = width / 2;
private double ballYPos = height / 2;
private int scoreP1 = 0;
private int scoreP2 = 0;
private boolean gameStarted;
private int playerOneXPos = 0;
private double playerTwoXPos = width - PLAYER_WIDTH;
public void start(Stage stage) throws Exception {
Canvas canvas = new Canvas(width, height);
GraphicsContext gc = canvas.getGraphicsContext2D();
Timeline tl = new Timeline(new KeyFrame(Duration.millis(10), e -> run(gc)));
tl.setCycleCount(Timeline.INDEFINITE);
canvas.setOnMouseMoved(e -> playerOneYPos = e.getY());
canvas.setOnMouseClicked(e -> gameStarted = true);
stage.setScene(new Scene(new StackPane(canvas)));
stage.show();
tl.play();
}
private void run(GraphicsContext gc) {
gc.setFill(Color.BLACK);
gc.fillRect(0, 0, width, height);
gc.setFill(Color.WHITE);
gc.setFont(Font.font(25));
if(gameStarted) {
ballXPos+=ballXSpeed;
ballYPos+=ballYSpeed;
if(ballXPos < width - width / 4) {
playerTwoYPos = ballYPos - PLAYER_HEIGHT / 2;
} else {
playerTwoYPos = ballYPos > playerTwoYPos + PLAYER_HEIGHT / 2 ?playerTwoYPos += 1: playerTwoYPos - 1;
}
gc.fillOval(ballXPos, ballYPos, BALL_R, BALL_R);
} else {
gc.setStroke(Color.YELLOW);
gc.setTextAlign(TextAlignment.CENTER);
gc.strokeText("Click to Start", width / 2, height / 2);
ballXPos = width / 2;
ballYPos = height / 2;
ballXSpeed = new Random().nextInt(2) == 0 ? 1: -1;
ballYSpeed = new Random().nextInt(2) == 0 ? 1: -1;
}
if(ballYPos > height || ballYPos < 0) ballYSpeed *=-1;
if(ballXPos < playerOneXPos - PLAYER_WIDTH) {
scoreP2++;
gameStarted = false;
}
if(ballXPos > playerTwoXPos + PLAYER_WIDTH) {
scoreP1++;
gameStarted = false;
}
if( ((ballXPos + BALL_R > playerTwoXPos) && ballYPos >= playerTwoYPos && ballYPos <= playerTwoYPos + PLAYER_HEIGHT) ||
((ballXPos < playerOneXPos + PLAYER_WIDTH) && ballYPos >= playerOneYPos && ballYPos <= playerOneYPos + PLAYER_HEIGHT)) {
ballYSpeed += 1 * Math.signum(ballYSpeed);
ballXSpeed += 1 * Math.signum(ballXSpeed);
ballXSpeed *= -1;
ballYSpeed *= -1;
}
gc.fillText(scoreP1 + "\t\t\t\t\t\t\t\t" + scoreP2, width / 2, 100);
gc.fillRect(playerTwoXPos, playerTwoYPos, PLAYER_WIDTH, PLAYER_HEIGHT);
gc.fillRect(playerOneXPos, playerOneYPos, PLAYER_WIDTH, PLAYER_HEIGHT);
}
}
view raw Pong.java hosted with ❤ by GitHub

Usando JavaFX para mostrar dados de repasse do governo federal

JavaFX é uma ótima plataforma para criar aplicações visuais. Recentemente criamos uma aplicação em JavaFX que permitiu visualizar os repasses do governo federal com facilidade e até uma postagem sobre isso foi escrita em um blog sobre dados abertos. Com essa aplicação fica muito fácil visualizar os dados de repasse sem ter que vascular o portal da transparência. Veja:


É possível também ver uma pequena animação da evolução dos repasses:



O código da aplicação está no meu github e para executa-la primeiro você precisa instalar o DrawingFX e em seguida você pode importar essa aplicação na sua IDE para executar a mesma (em breve mais melhorias na forma de execução serão feitas).



13 de jan. de 2017

Algoritmo Genético e um exemplo com JavaFX

Saindo do labirinto usando algoritmo genético




Um dos tópicos mais fascinantes no mundo da ciência da computação é Inteligência Artificial (IA). Uma sub-divisão no mundo da inteligência artificial são aqueles algoritmos que são baseados na natureza e nesse grupo temos os Algoritmos Genéticos.


Algoritmos Genéticos



Eu não irei me aprofundar nesse tópico, mas há ótimos materiais na internet.  A ideia geral é um algoritmo que leve em conta, dado um indíviduo, ele:

  • Tenha variação genética em diversos indivíduos (organismos também se aplica aqui);
  • Possa se reprodução e levar o código genético para suas gerações;
  • A cada reprodução haja ligeiras variações genéticas;
  • Uma "nota" para saber quão aquele indíviduo está adaptado ao ambiente atual.

Isso tudo é parte da teoria mais que comprovada de Darwin. O nome "teoria" confunde algumas pessoas e isso é incrível, acham que por ser uma teoria, é algo não confiável, mas interessantemente todas as evidências validam essa teoria desde que ela foi publicada! Sobre IA e Evolução, Pirulla fala um pouco disso no vídeo abaixo.



Para saber mais sugiro a página da Wikipédia sobre o assunto e o vídeo de uma aula do MIT e, de novo, um vídeo do Shiffman.







Algoritmos genéticos usando Java


Para colocar isso em prática usando em Java você pode desenvolver o seu próprio mini framework com DNA, Genes, cromossomos, algoritmos de mutação ou cruzamento e algoritmos de seleção natural, mas pra que reinventar a roda? No mundo Java temos algumas bibliotecas já conhecidas nesse meio, destaco a JGAP, um pouco antiga, e a moderna Jenetics, que usa Java 8 e os novos conceitos como Lambda, Method Reference, Stream, etc. O guia do usuário da API Jenetics é muito bom, cobre quase todas as classes que são usadas durante a programação. Mas ainda senti falta de exemplos mais claros, por isso, antes de tentar algo visual com JavaFX, eu criei 3 pequenas aplicações.


Seleciona o conjunto de double que tem a maior soma de algarismos; Esse é um exemplo besta, mas que mostra algumas classes importantes da API. A ideia é: dado um conjunto de double, qual tem a maior soma dos algarismos? Veja o código.
CODE

A saída é algo assim:

[[[85],[23],[62]]], sum 26
[[[42],[5],[53]]], sum 19
[[[4],[82],[14]]], sum 19
[[[17],[99],[41]]], sum 31
[[[32],[16],[23]]], sum 17
[[[15],[85],[38]]], sum 30
[[[54],[30],[51]]], sum 18
[[[31],[20],[76]]], sum 19
[[[88],[63],[69]]], sum 40
[[[52],[11],[6]]], sum 15
 (Um monte de passo intermediário)
[[[88],[97],[97]]], sum 48
[[[88],[97],[97]]], sum 48
[[[88],[29],[25]]], sum 34
[[[88],[97],[95]]], sum 46
[[[88],[97],[97]]], sum 48
Selected: [[[88],[97],[97]]]. Sum: 48


Veja que podemos resolver ele ser usar algoritmo genético, mas no código acima você pode começar a entender a pegada da API: Cromossomos tem um conjunto de genes, os genes são os objetos reais da sua aplicação (um número, uma String, um objeto do tipo pessoa...), genotype é o conjunto de cromossomos e criamos dois métodos importantes: um gera valores para serem trabalhados pela API (no exemplo acima usamos um cromossomo da própria API e ele tem o método pra gerar: Genotype.of(LongChromosome.of(1, 100, 3));) e o método que recebe o Genotype para que você possa trabalhar nele e retornar um ranking ou um número que indica quanto aquele genotype está adaptado ao ambiente (ver o método fitness).

import java.util.concurrent.atomic.AtomicInteger;
import org.jenetics.Genotype;
import org.jenetics.LongChromosome;
import org.jenetics.LongGene;
import org.jenetics.engine.Engine;
import org.jenetics.engine.EvolutionResult;
import org.jenetics.util.Factory;
/**
*
* Select the genotype with greatest algorithms sum
*
* @author wsiqueir
*
*/
public class LargestDigitSum {
private static Integer fitness(Genotype<LongGene> lt) {
LongChromosome lc = lt.getChromosome().as(LongChromosome.class);
AtomicInteger sum = new AtomicInteger(0);
lc.stream().forEach(l -> {
String str = String.valueOf(l.longValue());
for (char ch : str.toCharArray()) {
sum.addAndGet(Integer.parseInt(String.valueOf(ch)));
}
});
System.out.println(lt + ", sum " + sum.get());
return sum.get();
}
public static void main(String[] args) {
Factory<Genotype<LongGene>> gtf = Genotype.of(LongChromosome.of(1, 100, 3));
Engine<LongGene, Integer> engine = Engine.builder(LargestDigitSum::fitness, gtf).build();
Genotype<LongGene> result = engine.stream().limit(10).collect(EvolutionResult.toBestGenotype());
System.out.println("Selected: " + result + ". Sum: " + fitness(result));
}
}

Escrevendo uma frase: Se você conseguir 1 milhão de macacos e falar pra eles darem tapas no teclado de 1 milhão de computadores, qual seria a chance desse texto sair uma música da banda Queen? Raras, muito raras. Um programa com algoritmo genético pode chegar a esse resultado muito mais rápido e é claro que esse é somente outro exemplo. Veja abaixo um programa com Jenetics que consegue escrever uma frase que você escrever usando evolução:


import java.util.Random;
import org.jenetics.AnyChromosome;
import org.jenetics.AnyGene;
import org.jenetics.Genotype;
import org.jenetics.RouletteWheelSelector;
import org.jenetics.engine.Engine;
import org.jenetics.engine.EvolutionResult;
import org.jenetics.util.Factory;
/**
*
* Will learn how to write the sentence you chose
*
* @author wsiqueir
*
*/
public class SentenceWriter {
final static String SENTENCE = "Be or not to be?";
private static String POSSIBLE_CHARS;
static Random r = new Random();
public static Character generateRandomString() {
POSSIBLE_CHARS = " abcdefghijklmnopqrstuvxzwyABCDEFGHIJKLMNOPQRSTUVXZWY!?.,'";
int i = r.nextInt(POSSIBLE_CHARS.length());
return POSSIBLE_CHARS.charAt(i);
}
private static Integer fitness(Genotype<AnyGene<Character>> ch) {
int sum = 0;
for (int i = 0; i < SENTENCE.length(); i++) {
char target = SENTENCE.charAt(i);
char allele = ch.getChromosome().getGene(i).getAllele();
if (allele == target) {
sum += 100;
}
sum += POSSIBLE_CHARS.length() - Math.abs(target - allele);
}
return sum;
}
public static void main(String[] args) {
Factory<Genotype<AnyGene<Character>>> gtf = Genotype
.of(AnyChromosome.of(SentenceWriter::generateRandomString, SENTENCE.length()));
final Engine<AnyGene<Character>, Integer> engine = Engine.builder(SentenceWriter::fitness, gtf)
.offspringSelector(new RouletteWheelSelector<>()).build();
Genotype<AnyGene<Character>> result = engine.stream().limit(50000).collect(EvolutionResult.toBestGenotype());
System.out.println("Selected: " + result);
}
}


Escape do Labirinto: Esse é um exemplo um pouco mais elaborado. Nele temos um pequeno labirinto gerado e a função do algoritmo genético é evoluir o conjunto inicial de direções do labirinto para encontrar uma saída. Bem, esse código é um pouco extenso, assim peço para vocês verem no meu github. A ideia é ter genes que são customizados, do tipo Direction  e esses genes são uma possível saída para o labirinto, representado pela classe Maze. Incrivelmente, com algoritmo genético, as direções iniciais são mutadas de forma que conseguem encontrar uma saída do labirinto! Vejam uma tela:



Como esse é um blog sobre JavaFX, foi criada uma aplicação que desenha o labirinto e permite que você teste parâmetros para rodar o algoritmo! O código dele também é extenso e ninguém vai ler um blog com dezenas de linhas de código! Então, caso queiram estudar mais, aqui vão os passos para executar a aplicação (precisa de Maven e Java 8):


$ mvn clean package exec:java -Dexec.mainClass=org.fxapps.genetics.mazefx.MazeFXApp

O resultado pode ser visto no vídeo abaixo:

10 de jan. de 2017

Simples Relógio com JavaFX

O participante Welington Pietronero, do grupo JavaFX-BR, pediu um exemplo de um label que mostra as horas com JavaFX. Criamos um simples relógio:


A criação foi bem simples, segue aqui uma explicação rápida do código e em seguida o próprio código:


  • O texto vai ficar em um Label, no programa chamamos ele de lblRelogio;
  • Para transformar o objeto data em texto, precisamos de um SimpleDateFormat. Essa é uma classe bem bacana e ao criarmos uma instância da mesma, passamos uma String que representará a forma que a hora/data será representada. Há uma tabela na documentação dessa classe que mostra como podemos formatar a nossa data, no nosso caso usamos: hh:mm:ss a - que resulta no formato mostrado acima. Claro que há uma nova API de datas no Java 8, mas a API antiga ainda é muito usada!
  • No método start (se não entende o que faz esse método, por favor, veja esse post) criamos uma Font para o lblRelogio. Essa fonte aceita parâmetros que definem se a fonte ficará em negrito ou alguma variação de negrito ou se a fonte vai ficar em itálico ou normal. Lembrando que é possível usar CSS também;
  • Por fim, a chave do nosso código é a Timeline, que executa os KeyFrames no tempo passado como parâmetro. Um KeyFrame terá uma ação associada a ele e no nosso caso simplesmente chamamos o método atualizaHoras. A timeline executará "eternamente" o KeyFrame a cada 1 segundo.
Veja o codigo!

package org.fxapps.genetics;
import java.text.SimpleDateFormat;
import java.util.Date;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.effect.DropShadow;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.text.Font;
import javafx.scene.text.FontWeight;
import javafx.stage.Stage;
import javafx.util.Duration;
public class Relogio extends Application {
// criando o Label que vai informar as horas
private Label lblRelogio = new Label();
// SimpleDateFormat é a classe do Java que transforma datas para Strings
// usando o formato passado
private SimpleDateFormat formatador = new SimpleDateFormat("hh:mm:ss a");
// isso poderia ser emitido no Java 8
public static void main(String[] args) {
launch();
}
public void start(Stage stage) throws Exception {
// criamos a fonte usando o método de fábrica.
Font font = Font.font("Arial", FontWeight.EXTRA_BOLD, 60);
lblRelogio.setFont(font);
// vamos colocar um pequeno efeito no label pra deixar ele bonitin
lblRelogio.setEffect(new DropShadow(10, Color.RED));
// agora ligamos um loop infinito que roda a cada segundo e atualiza nosso label chamando atualizaHoras.
KeyFrame frame = new KeyFrame(Duration.millis(1000), e -> atualizaHoras());
Timeline timeline = new Timeline(frame);
timeline.setCycleCount(Timeline.INDEFINITE);
timeline.play();
// por fim criamos o stage, a scene com um stack pane e colocamos o nosso label lá
Scene cena = new Scene(new StackPane(lblRelogio), 450, 150);
stage.setScene(cena);
stage.show();
}
private void atualizaHoras() {
Date agora = new Date();
lblRelogio.setText(formatador.format(agora));
}
}
view raw Relogio.java hosted with ❤ by GitHub

8 de jan. de 2017

Super Formas com JavaFX


Mais uma postagem sobre efeitos visuais com JavaFX e hoje vamos falar sobre a aplicação da Superformula, derivada da fórmula Superellipse para gerar supershapes! Pode parecer confuso, mas vamos aplicar uma fórmula no JavaFX e gerar formas geométricas bacanas somente trocando os parâmetros da fórmula!
Claro a inspiração novamente veio do Shiffman! Veja abaixo o vídeo dele:


O código principal foi tirado do artigo do Paulo Bourke aplicando o algoritmo em um ângulo 0 até 2*PI temos diversas formas geradas.

O código é bem simples! Novamente usamos aquela classe utilitária para desenhar em JavaFX e a cada ciclo nós vamos aplicar a fórmula com novos parâmetros e então transformamos os resultados em pontos X e Y e desenhamos na tela pontinhos. O número de pontos define a resolução e para gerar todos os pontos fazemos um loop e pegamos um ângulo baseado no ponto atual do loop, aplicamos a fórmula e pegamos os pontos.

Em outras palavras, se quisermos 10 pontos, aplicamos a fórmula usando (0 * 10 / PI *2), (1 * 10 / PI *2), , (2 * 10 / PI *2), (1 * 10 / PI *2) e assim vai...

Mas o ponto principal são as constantes. Ao executar a fórmula, podemos mudar algumas constantes e como resultado temos formas geométricas completamente diferentes! As constantes são m, n1, n2 e n3!

A nossa aplicação muda os valores das  "constantes" e aplica a fórmula a cada frame executado. O resultado é o seguinte:


Os tipos de azul utilizados foram escolhidos pela minha amada Luana e o código da aplicação ficou bem compacto, pode ver abaixo:



Projeto completo no github

import org.fxapps.drawingfx.DrawingApp;
import static java.lang.Math.*;
import javafx.scene.paint.Color;
import javafx.scene.text.Font;
public class SuperShapesApp extends DrawingApp {
int numberOfPoints = 1300;
private double maxPhi = Math.PI * 2;
private double m = 1, n1 = 0.3, n2 = 0.3, n3 = 0.3;
public static void main(String[] args) {
launch();
}
public void setup() {
title = "Super Shapes";
width = 800;
height = 600;
frames = 1;
ctx.setFont(Font.font(30));
ctx.setStroke(Color.NAVY);
}
public void draw() {
ctx.setFill(Color.LIGHTBLUE);
ctx.fillRect(0, 0, width, height);
double[][] points = new double[numberOfPoints][2];
for (int i = 0; i < numberOfPoints; i++) {
double phi = toDegrees(i * (maxPhi) / numberOfPoints);
double r = superShape(m, n1, n2, n3, phi);
double x = 250 * r * cos(phi) + width / 2;
double y = 250 * r * sin(phi) + height / 2;
points[i][0] = x;
points[i][1] = y;
}
for (int i = 0; i < numberOfPoints; i++) {
ctx.strokeOval(points[i][0], points[i][1], 2, 2);
}
ctx.setFill(Color.WHITE);
String str = String.format("M=%.1f, N1=%.1f, N2=%.1f, N3=%.1f, PHI= %s",
m, n1, n2, n3, maxPhi == PI * 2 ? "2PI" : "12PI");
ctx.fillText(str, 50, height - 20);
m += 0.20;
if (m > 10) {
if (n1 == 1) {
n1 = n2 = n3 = 0.3;
} else if (maxPhi == (PI * 2)) {
maxPhi = PI * 12;
} else {
n1 = n2 = n3 = 1;
maxPhi = PI * 2;
}
m = 1;
}
}
private double superShape(double m, double n1, double n2, double n3, double phi) {
double r, t1, t2, a = 1, b = 1;
t1 = cos(m * phi / 4) / a;
t1 = abs(t1);
t1 = pow(t1, n2);
t2 = sin(m * phi / 4) / b;
t2 = abs(t2);
t2 = pow(t2, n3);
r = pow(t1 + t2, 1 / n1);
return abs(r) == 0 ? 0 : 1 / r;
}
}

7 de jan. de 2017

Desenvolvimento rápido de aplicações de efeitos visuais com JavaFX

Quem acompanha o blog notou que ultimamente temos postado somente aplicações mais visuais sempre se baseando nos vídeos do mestre Daniel Shiffman. Pois bem, ele usa Processing, uma outra linguagem de programação e esse é um blog sobre JavaFX e o nosso objetivo é também sempre se manter em contato com Java - em outras palavras, quero "levar a palavra" do Shiffman para o mundo e ainda exercitar Java. A palavra de Shiffman é a seguinte:

"Processing, for me, has always been just the most wonderful thing ever. It’s given me a mission and a passion, to bring computation to everyone: artists, designers, musicians, biologists, doctors, dancers, animators, bankers, photographers, librarians, fashion designers, architects, psychologists, journalists, and writers, just to name a few. Writing code can be scary, something many mistakenly think is reserved for computer scientists and engineers. Processing has helped eliminate that fear, making programming accessible to a wider audience, particularly artists.”

Fonte: Wikipedia

Ou seja, ele encontrou em processing uma forma de levar programação a mais pessoas. Com JavaFX não é diferente, é possível programar e criar aplicações sem ser um mestre Java - mas, a linguagem ainda aparece as vezes como um empecilho. Eu escrevo esse blog há 4 anos e estou "mexendo" com JavaFX desde a primeira versão, uns 8 anos já e vejo isso acontecendo.
Mas por que o blog então não é sobre Processing? O motivo é que Java é super importante para o mercado e "brincando" com JavaFX você exercita Java também, mas é necessário o equilibrio para que a linguagem Java não seja um empecilho.

Resumindo, criei uma classe que estou usando em aplicações visuais que "imita" o processing - mas é claro que o Processing é um universo a parte, a ideia é tirar partes repetitivas para que se tenha o foco na aplicação visual. O nome da classe é "DrawingApp", como já visto no post sobre as flores e no post sobre o algoritmo Metaballs.
Veja abaixo o exemplo de uma aplicação simples (bolas coloridas) que usa a DrawingApp e o código da classe DrawingApp.

package org.fxapps.drawingfx;
import javafx.scene.paint.Color;
public class BouncingBalls extends DrawingApp {
Ball[] balls = new Ball[20];
public static void main(String[] args) {
launch();
}
@Override
void setup() {
title = "Bouncing Ball Example";
width = 800;
height = 600;
for (int i = 0; i < balls.length; i++) {
balls[i] = new Ball(width / 2, height / 2);
}
}
@Override
void draw() {
ctx.setFill(Color.LIGHTGRAY);
ctx.fillRect(0, 0, width, height);
for (Ball ball : balls) {
ball.update();
ball.show();
}
}
class Ball {
int x, y, r, speedX, speedY;
Color c;
public Ball(int x, int y) {
this.x = x;
this.y = y;
this.c = Color.rgb(random.nextInt(255), random.nextInt(255), random.nextInt(255));
r = random.nextInt(100);
speedX = 40 - random.nextInt(40) - 20;
speedY = random.nextInt(40) - 20;
}
void update() {
this.x+= speedX;
this.y+= speedY;
if(x < 0 || x > width) {
speedX *= -1;
}
if(y < 0 || y > height) {
speedY *= -1;
}
}
void show() {
ctx.setFill(c);
ctx.fillOval(x, y, r, r);
}
}
}
package org.fxapps.drawingfx;
import static java.lang.Math.pow;
import static java.lang.Math.sqrt;
import java.util.Random;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.input.KeyEvent;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import javafx.util.Duration;
/**
*
* Class to quick start drawing with JavaFX. Just extend this class and create your application!
*
* @author wsiqueir
*
*/
public abstract class DrawingApp extends Application {
public int frames = 10;
public String title = "My App";
public static int width = 600;
public static int height = 400;
public static Random random = new Random();
Canvas canvas = new Canvas();
GraphicsContext ctx = canvas.getGraphicsContext2D();
@Override
public void start(Stage stage) throws Exception {
setup();
canvas.setHeight(height);
canvas.setWidth(width);
canvas.setOnMouseClicked(this::mouseCliked);
canvas.setOnMouseDragged(this::mouseDragged);
canvas.setOnMouseMoved(this::mouseMoved);
canvas.setOnMouseEntered(this::mouseEntered);
canvas.setOnMouseExited(this::mouseExited);
canvas.setOnKeyPressed(this::keyPressed);
canvas.setOnKeyTyped(this::keyTyped);
canvas.setOnKeyReleased(this::keyReleased);
StackPane raiz = new StackPane(canvas);
stage.setTitle(title);
stage.setScene(new Scene(raiz, width, height));
stage.show();
canvas.requestFocus();
KeyFrame frame = new KeyFrame(Duration.millis(1000 / frames), e -> draw());
Timeline timeline = new Timeline(frame);
timeline.setCycleCount(Timeline.INDEFINITE);
timeline.play();
}
// classical setup and draw methods from Processing
void setup() {
}
void draw() {
}
// event listeners - if user override the method with event, the method
// without event won't be called
public void mouseCliked(MouseEvent e) {
mouseCliked();
}
public void mouseDragged(MouseEvent e) {
mouseDragged();
}
public void mouseMoved(MouseEvent e) {
mouseMoved();
}
public void mouseEntered(MouseEvent e) {
mouseEntered();
}
public void mouseExited(MouseEvent e) {
mouseExited();
}
public void keyPressed(KeyEvent e) {
keyPressed();
}
public void keyTyped(KeyEvent e) {
keyTyped();
}
public void keyReleased(KeyEvent e) {
keyReleased();
}
public void mouseCliked() {
}
public void mouseDragged() {
}
public void mouseMoved() {
}
public void mouseEntered() {
}
public void mouseExited() {
}
public void keyPressed() {
}
public void keyTyped() {
}
public void keyReleased() {
}
/*
* Utility methods
*/
public double distance(double x, double y, double x2, double y2) {
return sqrt(pow(x2 - x, 2) + pow(y2 - y, 2));
}
}
view raw DrawingApp.java hosted with ❤ by GitHub

6 de jan. de 2017

Flores com JavaFX

Todo mundo sabe que a natureza pode ser representada matematicamente e muitas das fórmulas da natureza são conhecidas. O maior exemplo é a sequência de Fibonacci e como pode ser encontrada na natureza:




Pois bem, hoje vamos usar mais uma fórmula muito interessante que tomei conhecimento de mais um vídeo do mestre Shiffman, veja:



Filotaxia é o nome em português dessa área de estudo e você pode ler mais sobre ela na wikipédia. No Phyllotaxis você pode ter acesso a um documento e ao algoritmo que o Shiffman e eu usamos.
Mais uma vez usamos o canvas em uma aplicação que vai criar "plantas" aleatóriamente na tela. Para isso, ao contrário do que fez o Shiffman, vamos criar uma classe que vai desenhar a nossa plantinha.

Mas como funciona o algoritmo? A ideia é muito simples, temos um ângulo mágico e aplicamos uma fórmula sobre ele usando um número que é incrementado a cada frame de uma animação, por fim, aplicamos trigonometria para conseguir os pontos X e Y que iremos desenhar na tela. Em seguida, desenhamos os pontos gerados.
A ideia geral é essa! Usamos uma classe genérica com os elementos que mostramos no post anterior e o código ficou bastante limpo. Em breve falo sobre o DrawingFX...
Veja em seguida um screenshot da aplicação, o código, e um vídeo onde eu mostro partes do código e executo a aplicação.



import org.fxapps.drawingfx.DrawingApp;
import static java.lang.Math.*;
import javafx.scene.paint.Color;
public class FlowersApp extends DrawingApp {
Flower[] flowers = new Flower[50];
public static void main(String[] args) {
launch();
}
public void setup() {
title = "Flowers App";
width = 1200;
height = 800;
for (int i = 0; i < flowers.length; i++) {
flowers[i] = new Flower(random.nextInt(width),
random.nextInt(height));
}
frames = 300;
background = Color.LIGHTYELLOW;
}
public void draw() {
for (int i = 0; i < flowers.length; i++) {
Flower p = flowers[i];
p.update();
p.show();
if (p.grown) {
flowers[i] = new Flower(random.nextInt(width),
random.nextInt(height));
}
}
}
public class Flower {
int posX, posY, n, duration, size, c = 1;
double angle = 137.5, x, y;
Color color, stroke;
boolean grown = false;
public Flower(int posX, int posY) {
this.posX = posX;
this.posY = posY;
this.color = Color.rgb(random.nextInt(255),
random.nextInt(255),
random.nextInt(255));
this.stroke = Color.rgb(random.nextInt(255),
random.nextInt(255),
random.nextInt(255));
size = random.nextInt(10) + 5;
duration = random.nextInt(1500) + 100;
}
public void update() {
double a = n * angle;
double r = c * sqrt(n);
x = r * cos(a) + posX;
y = r * sin(a) + posY;
if (!grown) {
n++;
}
if (n > duration) {
grown = true;
}
}
public void show() {
ctx.setFill(color);
ctx.setStroke(stroke);
ctx.strokeOval(x, y, size, size);
ctx.fillOval(x, y, size, size);
}
}
}
view raw FlowersApp.java hosted with ❤ by GitHub



Por fim vejam esse vídeo rápido que fiz para demonstar a APP e falar um pouco do código.

5 de jan. de 2017

Efeito Metaballs com JavaFX

Falamos sobre como escrever pixel por pixel de um Canvas e o objetivo na verdade era essa postagem aqui.

Nessa postagem vamos criar o efeito Metaballs e iremos seguir o mesmo código explicado pelo MESTRE Shiffman no vídeo abaixo, mas iremos aplicar em JavaFX. Sugiro você ver o vídeo, ler o artigo sobre escrita de pixels com JavaFX e tentar fazer por você mesmo. Se não conseguir, em seguida você pode ver o código que criamos como exemplo.



Nosso simples projeto





Como vocês viram, o Shiffman usa Processing,a linguagem de programação mais bacana para criarmos efeitos visuais. A boa notícia é que tudo que fazemos com Processing podemos fazer com JavaFX. A versão original de Processing, na verdade, é baseada em Java (com versões para JavaScript e Python). Logo, tudo que você vê ele fazendo você consegue fazer também e em Java, ou seja, você se diverte e ainda exercita o uso de uma linguagem de mercado.
O nosso programa vai ter duas pequenas classes, veja o código e a seguir um vídeo que fiz explicando. Se tiver dúvidas você pode perguntar no nosso grupo ou nos comentários


class Blob {
double posX, posY;
double velX, velY;
double raio;
public Blob(double x, double y) {
super();
this.posX = x;
this.posY = y;
this.raio = 200;
velX = Main.random.nextFloat() * 10;
velY = Main.random.nextFloat() * 10;
}
public void update() {
posX += velX;
posY += velY;
if(posX < 0 || posX > Main.WIDTH) {
velX *= -1;
}
if(posY < 0 || posY > Main.HEIGHT) {
velY *= -1;
}
}
}
view raw Blob.java hosted with ❤ by GitHub
import static java.lang.Math.pow;
import static java.lang.Math.sqrt;
import java.util.Random;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import javafx.util.Duration;
public class Main extends Application {
public static final float WIDTH = 600;
public static final float HEIGHT = 400;
int COLOR_MODES = 7;
public static Random random = new Random();
Blob[] blobs = new Blob[12];
public static void main(String[] args) {
launch();
}
int selectedColorMode = 1;
boolean inverter = false;
private Canvas canvas;
@Override
public void start(Stage stage) throws Exception {
canvas = new Canvas(WIDTH, HEIGHT);
for (int i = 0; i < blobs.length; i++) {
blobs[i] = new Blob(random.nextFloat() * WIDTH, random.nextFloat() * HEIGHT);
}
canvas.setOnMouseClicked(e -> {
if (e.isControlDown())
inverter = !inverter;
else if (selectedColorMode > COLOR_MODES)
selectedColorMode = 1;
else
selectedColorMode++;
});
stage.setTitle("MetaBalls example with JavaFX");
stage.show();
stage.setScene(new Scene(new StackPane(canvas), WIDTH, HEIGHT));
KeyFrame frame = new KeyFrame(Duration.millis(100), e -> draw());
Timeline timeline = new Timeline(frame);
timeline.setCycleCount(Timeline.INDEFINITE);
timeline.play();
}
private void draw() {
GraphicsContext ctx = canvas.getGraphicsContext2D();
ctx.clearRect(0, 0, WIDTH, HEIGHT);
for (int x = 0; x < WIDTH; x++) {
for (int y = 0; y < HEIGHT; y++) {
int total = 0;
for (Blob blob : blobs) {
double d = distance(x, y, blob.posX, blob.posY);
total += 15 * (blob.raio / d);
}
if (total > 255)
total = 255;
Color color = selectColor(total);
ctx.getPixelWriter().setColor(x, y, color);
}
}
for (int i = 0; i < blobs.length; i++) {
blobs[i].update();
}
}
private Color selectColor(int total) {
int inverso = 255;
if (inverter) {
inverso = 255 % total;
}
Color color = Color.rgb(total, total, total);
switch (selectedColorMode) {
case (1):
color = Color.rgb(total, total, total);
break;
case (2):
color = Color.rgb(total, total, inverso);
break;
case (3):
color = Color.rgb(total, inverso, total);
break;
case (4):
color = Color.rgb(total, inverso, inverso);
break;
case (5):
color = Color.rgb(inverso, total, total);
break;
case (6):
color = Color.rgb(inverso, total, inverso);
break;
case (7):
color = Color.rgb(inverso, inverso, total);
}
return color;
}
private double distance(double x, double y, double x2, double y2) {
return sqrt(pow(x2 - x, 2) + pow(y2 - y, 2));
}
}
view raw Main.java hosted with ❤ by GitHub



Conclusão


E está aberta a temporada de efeitos especiais no JavaFX! Vamos criar coisas baseadas nos vídeos do Shiffman. Em breve voltamos com mais novidades.

4 de jan. de 2017

Escrevendo pixel por pixel de um Canvas

Já falamos aqui sobre o Canvas e um uso básico do mesmo. Hoje vamos mostrar um pouco mais desse poderoso node, mostrando como manipular os pontos de imagem ou o pixel.O pixel representa cada ponto da sua tela e cada ponto tem uma cor RGB (vermelho, verde e azul), adicionalmente também a opacidade do ponto, mas hoje só falaremos do RGB.

Escrevendo pixel a pixel usando o Canvas


Ao criar o canvas, temos que acessar o Graphics2d se quisermos trabalhar nele. O mesmo Graphics2d permite acesso a uma classe do tipo PixelWriter e com ela podemos configurar a cor de cada pixel de um canvas! Veja o exemplo mais básico possível que gera um canvas com pixels aleatórios:

import java.util.Random;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.image.PixelWriter;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
public class Main extends Application {
private static final int WIDTH = 800;
private static final int HEIGHT = 600;
public static void main(String[] args) {
launch();
}
@Override
public void start(Stage stage) throws Exception {
Canvas canvas = new Canvas(WIDTH, HEIGHT);
PixelWriter pixelWriter = canvas.getGraphicsContext2D().getPixelWriter();
Random random = new Random();
for (int i = 0; i < WIDTH; i++) {
for (int j = 0; j < HEIGHT; j++) {
float r = random.nextFloat();
float g = random.nextFloat();
float b = random.nextFloat();
pixelWriter.setColor(i, j, Color.color(r, g, b));
}
}
stage.setScene(new Scene(new StackPane(canvas), WIDTH, HEIGHT));
stage.setTitle("Manipulando Pixel a Pixel de um canvas");
stage.show();
}
}
view raw Main.java hosted with ❤ by GitHub


O código é simples:

- Inicialmente criamos um canvas e pegamos o pixel writer dele;
- Há um for de for que passará por cada posição do canvas - indo até a largura e a altura da aplicação;
- Nesse for de for criamos valores aleatórios para R (vermelho), G (verde) e B (azul);
- Então escrevemos o pixel usando o PixelWriter com uma cor que criamos a partir dos valores aleatórios;
- Por fim temos toda a já conhecida parte do JavaFX, que é de criar uma scene, uma raiz.

O resultado é algo similar ao seguinte sempre com valores aleatórios ao executar a aplicação:


Temos bastante pontos cinza, pois o ponto cinza equivale a valores iguais de RGB! 

Olhem mais um exemplo bacana:


Veja o código

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.image.PixelWriter;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import static java.lang.Math.*;
public class Vermelhao extends Application {
private static final float WIDTH = 800;
private static final float HEIGHT = 600;
public static void main(String[] args) {
launch();
}
@Override
public void start(Stage stage) throws Exception {
System.out.println(sqrt(pow(0 - 400, 2) + pow(0 - 300, 2)));
Canvas canvas = new Canvas(WIDTH, HEIGHT);
GraphicsContext ctx = canvas.getGraphicsContext2D();
final PixelWriter pixelWriter = ctx.getPixelWriter();
double maiorDistancia = sqrt(pow(0 - 800, 2) + pow(0 - 600, 2)) / 2;
for (int i = 0; i < WIDTH; i++) {
for (int j = 0; j < HEIGHT; j++) {
double d = Math.sqrt(Math.pow((WIDTH / 2) - i, 2) + Math.pow((HEIGHT / 2) - j, 2));
d = d / maiorDistancia;
pixelWriter.setColor(i, j, Color.color(1 - d, 0, 0));
}
}
stage.setTitle("Manipulando Pixel a Pixel de um canvas");
stage.show();
stage.setScene(new Scene(new StackPane(canvas), WIDTH, HEIGHT));
}
}
view raw Vermelhao.java hosted with ❤ by GitHub

Bem, essa foi uma postagem rápida. Pretendo criar algo bacana com isso, então precisava introduzir essa classe. Até mais, pessoal!