Related
I implement a Sequential quadratic programming (SQP) optimizer and use ojAlgo for the quadratic programming (QP) subproblem.
My question is:
How do I get hold of the "Lagrange multipliers" for the QP solution?
In the attached example code that solve an QP result.getMultipliers() only return an empty Optional.
package com.mycompany.testojalgo;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import org.ojalgo.matrix.Primitive64Matrix;
import org.ojalgo.optimisation.Expression;
import org.ojalgo.optimisation.ExpressionsBasedModel;
import org.ojalgo.optimisation.Optimisation;
import org.ojalgo.optimisation.Variable;
import org.ojalgo.structure.Access1D;
import org.ojalgo.type.StandardType;
import org.ojalgo.type.context.NumberContext;
public class ojAlgoQP {
public static void main(String[] args) {
testOjAlgoQuadraticProgramming();
}
public static void testOjAlgoQuadraticProgramming() {
// QP Example 16.2 p453 in 'Numerical Optimization', 2ed, (2006), Jorge Nocedal and Stephen J. Wright.
// minimize function F(x1,x2,x3) = 3*x1*x1 + 2*x1*x2 + x1*x3 + 2.5*x2*x2 + 2*x2*x3 + 2*x3*x3 - 8*x1 - 3*x2 - 3*x3
// x = [x1, x2, x3]'
// F(x) = 1/2*x'*H*x + x'*g
// constraints x1 + x3 = 3, x2 + x3 = 0
// A*x = b
//objectiveGradient
Primitive64Matrix g = Primitive64Matrix.FACTORY.rows(new double[][]{
{-8}, {-3}, {-3}
});
//objectiveHessian
Primitive64Matrix H = Primitive64Matrix.FACTORY.rows(new double[][]{
{6, 2, 1},
{2, 5, 2},
{1, 2, 4}
});
Variable x1 = new Variable("x1");
Variable x2 = new Variable("x2");
Variable x3 = new Variable("x3");
// constraint equations
Primitive64Matrix A = Primitive64Matrix.FACTORY.rows(new double[][]{
{1, 0, 1},
{0, 1, 1}
});
// required constraint values
Primitive64Matrix b = Primitive64Matrix.FACTORY.rows(new double[][]{
{3}, {0}
});
List<Variable> variables = new ArrayList<>();
variables.add(x1);
variables.add(x2);
variables.add(x3);
ExpressionsBasedModel model = new ExpressionsBasedModel(variables);
Expression energy = model.addExpression("Energy");
energy.setLinearFactors(variables, g);
//divide by two to express function using hessian.
energy.setQuadraticFactors(variables, H.divide(2));
energy.weight(BigDecimal.ONE);
//create constraint equations
for (int i = 0; i < A.countRows(); i++) {
Expression expression = model.addExpression("Constraint#"+i);
for (int j = 0; j < A.countColumns(); j++) {
expression.set(variables.get(j), A.get(i, j));
}
expression.level(b.get(i));
}
Optimisation.Result result = model.minimise();
NumberContext accuracy = StandardType.PERCENT.withPrecision(1);
boolean ok = model.validate(result, accuracy);
Optimisation.State v = result.getState();
// How do I get the multipliers
Optional<Access1D<?>> multipliers = result.getMultipliers();
double value1 = result.getValue();
// Get result and check value and constraint
Primitive64Matrix x = Primitive64Matrix.FACTORY.rows(new double[][]{
{x1.getValue().doubleValue()}, {x2.getValue().doubleValue()}, {x3.getValue().doubleValue()}
});
//divide by two to express function using hessian, again.
Primitive64Matrix value = x.transpose().multiply(H.divide(2)).multiply(x).add(x.transpose().multiply(g));
Primitive64Matrix residual= A.multiply(x).subtract(b);
}
}
Update 1:
Here is my reworked example using org.ojalgo.optimisation.convex.ConvexSolver.getBuilder();
package com.mycompany.testojalgo;
import java.util.Optional;
import org.ojalgo.matrix.store.MatrixStore;
import org.ojalgo.matrix.store.Primitive64Store;
import org.ojalgo.optimisation.Optimisation;
import org.ojalgo.optimisation.convex.ConvexSolver;
import org.ojalgo.structure.Access1D;
public class ojAlgoQP {
public static void main(String[] args) {
testOjAlgoQuadraticProgramming2();
}
public static void testOjAlgoQuadraticProgramming2() {
// QP Example 16.2 p453 in 'Numerical Optimization', 2ed, (2006), Jorge Nocedal and Stephen J. Wright.
// minimize function F(x1,x2,x3) = 3*x1*x1 + 2*x1*x2 + x1*x3 + 2.5*x2*x2 + 2*x2*x3 + 2*x3*x3 - 8*x1 - 3*x2 - 3*x3
// x = [x1, x2, x3]'
// F(x) = 1/2*x'*H*x + x'*g
// constraints x1 + x3 = 3, x2 + x3 = 0
// A*x = b
//objectiveGradient
Primitive64Store gStore = Primitive64Store.FACTORY.rows(new double[][]{
{-8}, {-3}, {-3}
});
//objectiveHessian
Primitive64Store HStore = Primitive64Store.FACTORY.rows(new double[][]{
{6, 2, 1},
{2, 5, 2},
{1, 2, 4}
});
// constraint equations
Primitive64Store AStore = Primitive64Store.FACTORY.rows(new double[][]{
{1, 0, 1},
{0, 1, 1}
});
// required constraint values
Primitive64Store bStore = Primitive64Store.FACTORY.rows(new double[][]{
{3}, {0}
});
ConvexSolver.Builder builder = ConvexSolver.getBuilder();
builder.equalities(AStore, bStore);
builder.objective(HStore, gStore.negate());
ConvexSolver solver = builder.build();
Optimisation.Result result = solver.solve();
// How do I get the multipliers? multipliers = Optional.empty
Optional<Access1D<?>> multipliers = result.getMultipliers();
// value1 = -3.5
double value1 = result.getValue();
// Verify result:
// x= [2.0, -0.9999999999999996, 0.9999999999999997]';
// value = -3.5
// residual =[-4.440892098500626E-16, 1.1102230246251565E-16]'
Primitive64Store x = Primitive64Store.FACTORY.column(result.toRawCopy1D());
MatrixStore<Double> value = x.transpose().multiply(HStore.multiply(0.5)).multiply(x).add(x.transpose().multiply(gStore));
MatrixStore<Double> residual = AStore.multiply(x).subtract(bStore);
}
}
I believe that is an Optional because it was (sometimes) too messy to map the Lagrange multipliers from the solver to the constraints of the model.
If you're implementing an SQP solver may I suggest that you don't implement it in terms of ExpressionsBasedModel, but delegate to the convex solvers directly. Build something that implements org.ojalgo.optimisation.Optimisation.Solver and delegate to the various classes in the org.ojalgo.optimisation.convex package. Then you code more directly with the matrices, vectors and multipliers.
To make that solver usable by ExpressionsBasedModel you also implement an org.ojalgo.optimisation.Optimisation.Integration and register that by calling ExpressionsBasedModel.addPreferredSolver(myIntegeration) or ExpressionsBasedModel.addFallbackSolver(myIntegeration).
Implementing a solver and making it usable from the modelling tool are two separate things.
I am trying to implement a machine learning algorithm (k-nn for example).As it stands my Main class, which essentially builds 8×8-pixel matrices into an array to be manipulated later. (See the data description and sample dataset.) As it stands my arrays are printing as a like so:
, Class Code: 7 DataSet:[0, 0, 3, 9, 15, 8, 0, 0, 0, 1, 15, 16, 16, 7, 0, 0, 0, 0, 5, 16, 16, 10, 4, 0, 0, 0, 3, 16, 16, 16, 9, 0, 0, 0, 0, 15, 14, 4, 0, 0, 0, 0, 0, 13, 5, 0, 0, 0, 0, 0, 1, 15, 3, 0, 0, 0, 0, 0, 4, 13, 0, 0, 0, 0, 7]
Now for my starting point I'm looking to try to implement a very basic kNN algorithm as something to build from but I am having trouble manipulating the datasets that are being outputted. I have been reading up on Foundations of Machine Learning by M. Mohri but it hasn't been of any help. My Main class for building my data:
import java.util.*;
import java.io.*;
public class Main {
static class Data {
int[] dataSet;
int classCode;
public Data(int[] dataSet, int label) {
this.dataSet = dataSet;
this.classCode = label;
}
#Override
public String toString() {
return "Class Code: " + classCode + " DataSet:" + Arrays.toString(dataSet) + "\n";
}
}
ArrayList<Data> dataSetList;
int[][] dataArray = new int[2810][65];
private void readFile(String csvFile) {
int instances = 0;
dataSetList = new ArrayList<>();
try {
Scanner scan = new Scanner(new BufferedReader(new FileReader(csvFile)));
while (scan.hasNext()) {
String line = scan.next();
String[] extractedDataFromFile = line.split(",");
for (int i = 0; i < extractedDataFromFile.length; i++) {
dataArray[instances][i] = Integer.parseInt(extractedDataFromFile[i]);
}
dataSetList.add(new Data(dataArray[instances], dataArray[instances][extractedDataFromFile.length - 1]));
instances++;
}
System.out.println(dataSetList.toString());
} catch (FileNotFoundException ex) {
System.out.println(ex.getMessage());
}
}
public static void main(String[] args) {
Main main = new Main();
main.readFile("dataset1.csv");
}
}
This is my first time experimenting with machine learning so any feedback or approach to this would be hugely appreciated.
EDIT//
I'm looking at a basic kNN implementation as a starting point whether someone can redirect me to material on implementing on a similar data set or an example using my current provided code. I apologize if my initial post was a little vague
I tried using ANN_MLP in OpenCV 3.0 to train a model for a simple XOR operation (i.e. 00->0, 01->1, 10->1, 11->0). But it returned NaN when I called nn.predict. What's wrong with the code? Here is my Java code:
package jm.app;
import org.opencv.core.Core;
import org.opencv.core.CvType;
import org.opencv.core.Mat;
import org.opencv.core.TermCriteria;
import org.opencv.ml.ANN_MLP;
import org.opencv.ml.Ml;
import org.opencv.ml.StatModel;
public class Main {
static{System.loadLibrary(Core.NATIVE_LIBRARY_NAME);}
public static void main(String[] args) {
Mat trainData = new Mat(4, 2, CvType.CV_32FC1);
trainData.put(0, 0, 0);
trainData.put(0, 1, 0);
trainData.put(1, 0, 0);
trainData.put(1, 1, 1);
trainData.put(2, 0, 1);
trainData.put(2, 1, 0);
trainData.put(3, 0, 1);
trainData.put(3, 1, 1);
Mat trainLabels = new Mat(4, 1, CvType.CV_32FC1);
trainLabels.put(0, 0, 0);
trainLabels.put(1, 0, 1);
trainLabels.put(2, 0, 1);
trainLabels.put(3, 0, 0);
ANN_MLP nn = ANN_MLP.create();
nn.setActivationFunction(ANN_MLP.SIGMOID_SYM);
nn.setTrainMethod(ANN_MLP.BACKPROP);
nn.setBackpropMomentumScale(0.1);
nn.setBackpropWeightScale(0.1);
nn.setTermCriteria(new TermCriteria(TermCriteria.MAX_ITER, (int)100000, 0.000001));
Mat layers = new Mat(1, 3, CvType.CV_32SC1);
layers.put(0, 0, 2);
layers.put(0, 1, 3);
layers.put(0, 2, 1);
nn.setLayerSizes(layers);
nn.train(trainData, Ml.ROW_SAMPLE, trainLabels);
Mat testData = new Mat(1, 2, CvType.CV_32FC1);
testData.put(0, 0, 1);
testData.put(0, 1, 1);
Mat testLabels = new Mat(1, 1, CvType.CV_32FC1);
float res = nn.predict(testData, testLabels, ANN_MLP.RAW_OUTPUT);
Util.printMat(testLabels);
Mat layer1 = nn.getWeights(0);
Mat layer2 = nn.getWeights(1);
Mat layer3 = nn.getWeights(2);
Util.printMat(layer1);
Util.printMat(layer2);
Util.printMat(layer3);
}
}
package jm.app;
import org.opencv.core.Mat;
public class Util {
public static void printMat(Mat mat){
System.out.println();
System.out.print(mat.rows() + " * " + mat.cols());
for(int i = 0; i < mat.rows(); i++){
System.out.println();
for(int j = 0; j < mat.cols(); j++){
System.out.print(mat.get(i, j)[0] + " ");
}
}
System.out.println();
}
}
And the output is:
1 * 1
NaN
1 * 4
2.0 -1.0 2.0 -1.0
3 * 3
-0.417962425638773 -0.11805564491195578 0.7527567170648859
0.40930192249590086 -0.24876980957807385 -0.2929439299929529
0.6025307693048867 0.2936134607392147 -0.10605986687856579
4 * 1
0.5558049015443158
0.4766362469511742
0.3713056187114578
-0.24058588929784652
So I have two questions:
1. Why the "testLabel" got a NaN value?
2. Why the "layer1" is a 1*4 matrix? What did the "layer1" do here?
I tried adding another set of values to the demo example of stacked bar charts with achartengine, but the new values introduced by me don't appear on the chart. Is Stacking bars limited to two bars?
public Intent getIntent(Context context) {
String[] titles = new String[] { "Good", "Defect", "Repair" };
List<double[]> values = new ArrayList<double[]>();
values.add(new double[] { 14230, 12300, 1424, 15240, 15900, 19200,
22030, 21200, 19500, 15500, 12060, 14000 });
values.add(new double[] { 14230, 12300, 14240, 15244, 15900, 19200,
22030, 21200, 19500, 15500, 12600, 14000 });
values.add(new double[] { 5230, 7300, 9240, 10540, 7900, 9200, 12030,
11200, 9500, 10500, 11600, 13500 });
int[] colors = new int[] { Color.GREEN, Color.YELLOW, Color.RED };
XYMultipleSeriesRenderer renderer = buildBarRenderer(colors);
setChartSettings(renderer, "Machine Efficiency Rates",
"Machine", "NET produce", 0.5, 12.5, 0, 24000, Color.GRAY,
Color.LTGRAY);
renderer.getSeriesRendererAt(0).setDisplayChartValues(true);
renderer.getSeriesRendererAt(1).setDisplayChartValues(true);
renderer.getSeriesRendererAt(2).setDisplayChartValues(true);
renderer.setXLabels(12);
renderer.setYLabels(5);
renderer.setXLabelsAlign(Align.LEFT);
renderer.setYLabelsAlign(Align.LEFT);
renderer.setPanEnabled(true, false);
renderer.setZoomEnabled(true);
renderer.setZoomRate(1.1f);
renderer.setBarSpacing(0.5f);
return ChartFactory.getBarChartIntent(context,
buildBarDataset(titles, values), renderer, Type.STACKED);
}
There is a misunderstanding in the way AChartEngine displays stacked bar charts. It doesn't really stack them, but displays them one over the other. This means that you will want to always add the bigger values first then the smaller and so on as it renders the first series, the second one above the first and so on.
UPDATE: As of version 1.2.0, there is the new HEAP stacked bar charts, which are displayed in the manner you need.
I could not find a good answer. However, you can use the getCombinedXYChartIntent
to create a multiple stacked bars.
I use the CombinedTemperatureChart as a template and modified to achieve this.
The initial raw code is this
package org.achartengine.chartdemo.demo.chart;
import java.util.ArrayList;
import java.util.List;
import org.achartengine.ChartFactory;
import org.achartengine.chart.BarChart;
import org.achartengine.chart.BubbleChart;
import org.achartengine.chart.CubicLineChart;
import org.achartengine.chart.LineChart;
import org.achartengine.chart.PointStyle;
import org.achartengine.model.XYMultipleSeriesDataset;
import org.achartengine.model.XYSeries;
import org.achartengine.model.XYValueSeries;
import org.achartengine.renderer.XYMultipleSeriesRenderer;
import org.achartengine.renderer.XYSeriesRenderer;
import android.content.Context;
import android.content.Intent;
import android.graphics.Color;
import android.graphics.Paint.Align;
/**
* Combined temperature demo chart.
*/
public class CombinedTemperatureChart extends AbstractDemoChart {
/**
* Returns the chart name.
*
* #return the chart name
*/
public String getName() {
return "Combined temperature";
}
/**
* Returns the chart description.
*
* #return the chart description
*/
public String getDesc() {
return "The average temperature in 2 Greek islands, water temperature and sun shine hours (combined chart)";
}
/**
* Executes the chart demo.
*
* #param context the context
* #return the built intent
*/
public Intent execute(Context context) {
String[] titles = new String[] { "Crete Air Temperature", "Skiathos Air Temperature" };
List<double[]> x = new ArrayList<double[]>();
for (int i = 0; i < titles.length; i++) {
x.add(new double[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 });
}
List<double[]> values = new ArrayList<double[]>();
values.add(new double[] { 12.3, 12.5, 13.8, 16.8, 20.4, 24.4, 26.4, 26.1, 23.6, 20.3, 17.2, 13.9 });
values.add(new double[] { 9, 10, 11, 15, 19, 23, 26, 25, 22, 18, 13, 10 });
int[] colors = new int[] { Color.GREEN, Color.rgb(200, 150, 0) };
PointStyle[] styles = new PointStyle[] { PointStyle.CIRCLE, PointStyle.DIAMOND };
XYMultipleSeriesRenderer renderer = buildRenderer(colors, styles);
//renderer.setPointSize(5.5f);
int length = renderer.getSeriesRendererCount();
for (int i = 0; i < length; i++) {
XYSeriesRenderer r = (XYSeriesRenderer) renderer.getSeriesRendererAt(i);
r.setDisplayChartValues(true);
r.setChartValuesTextSize(25);
//r.setLineWidth(2);
r.setFillPoints(true);
}
setChartSettings(renderer, "Weather data", "Month", "Temperature", -1, 12.5, 0, 40,
Color.LTGRAY, Color.LTGRAY);
renderer.setXLabels(12);
renderer.setYLabels(10);
renderer.setShowGrid(true);
renderer.setXLabelsAlign(Align.RIGHT);
renderer.setYLabelsAlign(Align.RIGHT);
renderer.setZoomButtonsVisible(true);
renderer.setPanLimits(new double[] { -10, 20, -10, 40 });
renderer.setZoomLimits(new double[] { -10, 20, -10, 40 });
XYValueSeries sunSeries = new XYValueSeries("Sunshine hours");
sunSeries.add(0.5, 17);
sunSeries.add(1.5, 18);
sunSeries.add(2.5, 19);
sunSeries.add(3.5, 19);
sunSeries.add(4.5, 20.8);
sunSeries.add(5.5, 24.9);
sunSeries.add(6.5, 26);
sunSeries.add(7.5, 27.8);
sunSeries.add(8.5, 28.4);
sunSeries.add(9.5, 25.5);
sunSeries.add(10.5, 23.5);
sunSeries.add(11.5, 19.5);
XYSeriesRenderer lightRenderer = new XYSeriesRenderer();
lightRenderer.setColor(Color.YELLOW);
XYSeries waterSeries = new XYSeries("Water Temperature");
waterSeries.add(0.5, 16);
waterSeries.add(1.5, 15);
waterSeries.add(2.5, 16);
waterSeries.add(3.5, 17);
waterSeries.add(4.5, 20);
waterSeries.add(5.5, 23);
waterSeries.add(6.5, 25);
waterSeries.add(7.5, 25.5);
waterSeries.add(8.5, 26.5);
waterSeries.add(9.5, 24);
waterSeries.add(10.5, 22);
waterSeries.add(11.5, 18);
renderer.setBarSpacing(2);
renderer.setBarWidth(2);
XYSeriesRenderer waterRenderer = new XYSeriesRenderer();
waterRenderer.setColor(Color.argb(250, 0, 210, 250));
XYMultipleSeriesDataset dataset = buildDataset(titles, x, values);
dataset.addSeries(0, sunSeries);
dataset.addSeries(1, waterSeries);
renderer.setBarWidth(0.5f);
renderer.addSeriesRenderer(lightRenderer);
renderer.addSeriesRenderer(waterRenderer);
waterRenderer.setDisplayChartValues(true);
waterRenderer.setChartValuesTextSize(25);
lightRenderer.setDisplayChartValues(true);
lightRenderer.setChartValuesTextSize(25);
String[] types = new String[] { BarChart.TYPE, BarChart.TYPE, BarChart.TYPE, BarChart.TYPE };
Intent intent = ChartFactory.getCombinedXYChartIntent(context, dataset, renderer, types,
"Weather parameters");
return intent;
}
}
I hope someone still need this, it take a some time figure it out.
Unfortunately, I don't have the points to paste an image.
I posted a question in sun java forums sometime ago and i am finding it hard to understand the first response i received from the replier though it seems he gave me the correct approach to my problem. The link to the question is:
http://forums.sun.com/thread.jspa?threadID=5436562&tstart=0
Someone replied that i should use BufferedImage and make tiles. I don't really understand what the tiles mean in connection with the BufferedImage.
I would like someone to explain to me what the tiles are and how they are created in the BufferedImage.
I have searched the web for a while but couldn't find any link that can help me understanding the basics of the tiles and creating the tiles. Any pointer to a site is also appreciated.
I need help in understanding the tiles in connection with the BufferedImage and also how they are created.
A "tile" in a 2D game simply means an "image smaller than whole screen that you can reuse several times to create the background".
Here's a a working example where four tiles are created (adding some random noise to every pixel). Each tile is 50x50 pixels.
Then there's a "map" (that you call a "grid" in your case) representing which tiles you want to put where.
From that map, a bigger BufferedImage is created (note that it's just an example, in a real program you'll want to use a BufferedImage copy, not a pixel-by-pixel copy).
The map is 9x7, each tile is 50x50 pixels, hence the resulting image is 9*50 x 7*50 (ie 450 by 350).
Note that the following is really just a simple example, as short as possible, showing how to create a bigger BufferedImage using several tiles: the goal is not to give a tutorial on best Swing usage nor on how to squeeze every bit of performances out of BufferedImages, etc.
import javax.swing.*;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.util.Random;
public class ToyTiled extends JFrame {
private static final int IMAGE_TYPE = BufferedImage.TYPE_INT_ARGB;
private BufferedImage img;
public static void main( String[] args ) {
new ToyTiled();
}
public ToyTiled() {
super();
this.add(new JPanel() {
#Override
protected void paintComponent(Graphics g) {
g.drawImage(img, 0, 0, null);
}
});
img = new BufferedImage( 450, 350, IMAGE_TYPE ); // here you should create a compatible BufferedImage
this.setSize(img.getWidth(), img.getHeight());
this.setDefaultCloseOperation(DISPOSE_ON_CLOSE);
final int NB_TILES = 4;
BufferedImage[] tiles = new BufferedImage[NB_TILES];
tiles[0] = createOneTile( new Color( 255, 255, 255 ) );
tiles[1] = createOneTile( new Color( 255, 0, 255 ) );
tiles[2] = createOneTile( new Color( 0, 0, 255 ) );
tiles[3] = createOneTile( new Color( 0, 255, 255 ) );
final int[][] map = new int[][] {
{ 1, 0, 2, 3, 0, 1, 2, 2, 2 },
{ 0, 2, 3, 0, 1, 2, 2, 2, 3 },
{ 1, 0, 2, 3, 0, 1, 2, 2, 2 },
{ 2, 1, 0, 1, 2, 3, 2, 0, 0 },
{ 1, 0, 2, 3, 0, 1, 2, 2, 3 },
{ 1, 0, 2, 2, 1, 1, 2, 2, 3 },
{ 1, 0, 2, 3, 0, 1, 2, 2, 3 },
};
for (int i = 0; i < map[0].length; i++) {
for (int j = 0; j < map.length; j++) {
final BufferedImage tile = tiles[map[j][i]];
for (int x = 0; x < tile.getWidth(); x++) {
for (int y = 0; y < tile.getHeight(); y++) {
img.setRGB( x + i * 50, y + j * 50, tile.getRGB(x,y) );
}
}
}
}
this.setVisible( true );
}
private BufferedImage createOneTile( final Color c ) {
final Random r = new Random();
final BufferedImage res = new BufferedImage( 50, 50, IMAGE_TYPE );
for (int x = 0; x < res.getWidth(); x++) {
for (int y = 0; y < res.getHeight(); y++) {
res.setRGB( x, y, c.getRGB() - r.nextInt(150) );
}
}
return res;
}
}
If you want to rotate a portion of a BufferedImage you might find these classes/methods useful:
AffineTransform.getRotateInstance or AffineTransform.getQuadrantRotateInstance
AffineTransformOp
BufferedImage.getSubImage()
BufferedImage.setData
Example:
import java.awt.Component;
import java.awt.event.ActionEvent;
import java.awt.geom.AffineTransform;
import java.awt.image.AffineTransformOp;
import java.awt.image.BufferedImage;
import java.awt.image.BufferedImageOp;
import java.io.IOException;
import java.net.URL;
import javax.imageio.ImageIO;
import javax.swing.AbstractAction;
import javax.swing.BoxLayout;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.WindowConstants;
public class TileTest extends JFrame {
public static void main(String[] args) throws IOException {
URL logo = new URL("http://sstatic.net/so/img/logo.png");
TileTest tileTest = new TileTest(ImageIO.read(logo));
tileTest.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
tileTest.setVisible(true);
}
private TileTest(BufferedImage image) throws IOException {
this.image = image;
setLayout(new BoxLayout(getContentPane(), BoxLayout.Y_AXIS));
JLabel label = new JLabel(new ImageIcon(image));
add(label);
BufferedImage tile = image.getSubimage(0, 0, 61, 61);
add(new JButton(new RotateAction(tile, label)));
pack();
}
private BufferedImage image;
}
class RotateAction extends AbstractAction {
public void actionPerformed(ActionEvent e) {
BufferedImage tmpImage = op.filter(image, null);
image.setData(tmpImage.getRaster());
component.repaint();
}
RotateAction(BufferedImage image, Component component) {
super("Rotate");
this.component = component;
this.image = image;
double x = 0.5 * image.getWidth();
double y = 0.5 * image.getHeight();
AffineTransform xfrm =
AffineTransform.getQuadrantRotateInstance(1, x, y);
op = new AffineTransformOp(
xfrm, AffineTransformOp.TYPE_NEAREST_NEIGHBOR);
}
private final Component component;
private final BufferedImage image;
private final BufferedImageOp op;
}