Flood-It-Bot/main/ImageUtils.java
2012-01-11 16:55:25 +02:00

369 lines
14 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package main;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.util.Random;
/**
* Класс обработки изображений
* @author aNNiMON
*/
public class ImageUtils {
/* За сколько точек мы будем узнавать преобладающий фон */
private static final int MAX_COLOR_POINTS = 50;
/* Чувствительность к поиску кнопок */
private static final int FIND_BUTTON_TOLERANCE = 20;
/* Изображение окна */
private BufferedImage image;
/* Размер изображения */
private int w, h;
/* Размерность поля */
private int boardSize;
/* Размер ячеек */
private int cellSize;
/* Координата угла игрового поля */
private Point board;
/* Монохромное представление изображения */
private boolean[] monochrome;
/**
* Конструктор для определения настроек
* @param image
* @param boardSize
*/
public ImageUtils(BufferedImage image, int boardSize) {
this.image = image;
this.boardSize = boardSize;
w = image.getWidth();
h = image.getHeight();
}
/**
* Конструктор для проверки настроек
* @param image
* @param boardSize
* @param cellSize
* @param x
* @param y
*/
public ImageUtils(BufferedImage image, int boardSize, int cellSize, int x, int y) {
this.image = image;
this.boardSize = boardSize;
this.cellSize = cellSize;
this.board = new Point(x, y);
w = image.getWidth();
h = image.getHeight();
}
/**
* Получить размер ячейки
* @return
*/
public int getCellSize() {
return cellSize;
}
/**
* Получить координаты игрового поля
* @return точка с координатами левого верхнего угла поля
*/
public Point getBoardParameters() {
int[] pixels = new int[w * h];
image.getRGB(0, 0, w, h, pixels, 0, w);
monochrome = threshold(pixels, 64);
board = getBoardXY(boardSize);
return board;
}
/**
* Получить изображение игрового поля
* @return картинка игрового поля
*/
public BufferedImage getBoardImage() {
int size = cellSize * boardSize;
try {
return image.getSubimage(board.x, board.y, size, size);
} catch (Exception e) {
return image;
}
}
/**
* Получить координаты кнопок для автоматического нажатия
* @param colors массив цветов, по которым будем искать кнопки
* @return массив координат с точками, или null - если не удалось найти
*/
public Point[] getButtons(int[] colors) {
Point[] out = new Point[colors.length];
// Размер игрового поля в пикселах
int size = boardSize * cellSize;
// Размеры частей изображения, на которых будем искать кнопки
Rectangle[] partsOfImage = new Rectangle[] {
new Rectangle(0, board.y, board.x, size), // слева от поля
new Rectangle(0, 0, w, board.y), // сверху от поля
new Rectangle(board.x+size, board.y,
w-board.x-size, size), // справа от поля
new Rectangle(0, board.y+size,
w, h-board.x-size) // снизу от поля
};
for (int i = 0; i < partsOfImage.length; i++) {
Rectangle rect = partsOfImage[i];
BufferedImage part = image.getSubimage(rect.x, rect.y, rect.width, rect.height);
// Вырезаем часть изображения, в котором будем искать
boolean found = true;
for (int j = 0; j < colors.length; j++) {
if (colors[i] == -1) continue;
Point pt = findButton(part, colors[j]);
if (pt != null) {
// Учитываем смещения относительно частей картинок
pt.translate(rect.x, rect.y);
out[j] = pt;
} else {
found = false;
break;
}
}
if (found) return out;
}
// Не удалось найти все точки
return null;
}
/**
* Преобразовать массив цветов в графический вид
* @param ids массив идентификаторов последовательности
* @param palette массив палитры цветов
* @return изображение последовательности цветов
*/
public BufferedImage sequenceToImage(byte[] ids, int[] palette) {
final int size = 20; // размер каждой ячейки
// Разбивать будем по 10 клеток на строку
final int CELLS_IN_ROW = 10;
int width = CELLS_IN_ROW * size;
if (width == 0) width = size;
int rows = ids.length / CELLS_IN_ROW;
BufferedImage out = new BufferedImage(width, (rows*size)+size, BufferedImage.TYPE_INT_RGB);
Graphics G = out.getGraphics();
for (int i = 0; i < ids.length; i++) {
G.setColor(new Color(palette[ids[i]]));
G.fillRect(i % CELLS_IN_ROW * size,
i / CELLS_IN_ROW * size,
size, size);
}
G.dispose();
return out;
}
/**
* Преобразовать цветное изображение в монохромное.
* Нужно также учесть, что если поле расположено на светлом
* фоне, то необходимо инвертировать изображение, чтобы
* получить сплошную белую область на месте поля.
* @param pixels массив пикселей изображения
* @param value разделяющее значение
* @return массив boolean, true - белый, false - чёрный
*/
private boolean[] threshold(int[] pixels, int value) {
boolean inverse = isBackgroundLight(MAX_COLOR_POINTS);
if (inverse) value = 255 - value;
boolean[] bw = new boolean[pixels.length];
for (int i = 0; i < pixels.length; i++) {
int brightNess = getBrightness(pixels[i]);
bw[i] = (brightNess >= value) ^ inverse;
}
return bw;
}
/**
* Получение состояния яркости фона.
* @param numPoints сколько точек нужно для определения.
* @return true - фон светлый, false - тёмный
*/
private boolean isBackgroundLight(int numPoints) {
// Получаем numPoints случайных точек
Random rnd = new Random();
int[] colors = new int[numPoints];
for (int i = 0; i < numPoints; i++) {
int x = rnd.nextInt(w);
int y = rnd.nextInt(h);
colors[i] = image.getRGB(x, y);
}
// Находим среднюю яркость всех numPoints точек
long sum = 0;
for (int i = 0; i < numPoints; i++) {
int brightness = getBrightness(colors[i]);
sum = sum + brightness;
}
sum = sum / numPoints;
return (sum > 128);
}
/**
* Определить координаты левой верхней ячейки игрового поля.
* @param boardSize размерность поля (10x10, 14x14 и т.д.)
* @return координата левого верхнего прямоугольника
*/
private Point getBoardXY(int boardSize) {
/*
* Сначала подсчитаем количество белых точек по горизонтали и вертикали
*/
int[] horizontal = new int[h];
for (int i = 0; i < h; i++) {
int count = 0;
for (int j = 0; j < w; j++) {
if (getBWPixel(j, i)) count++;
}
horizontal[i] = count;
}
int[] vertical = new int[w];
for (int i = 0; i < w; i++) {
int count = 0;
for (int j = 0; j < h; j++) {
if (getBWPixel(i, j)) count++;
}
vertical[i] = count;
}
/*
* Затем "отфильтруем" лишнее: подсчитаем среднее значение
* и на его основе уберём малозначимые строки и столбцы.
*/
horizontal = filterByMean(horizontal);
vertical = filterByMean(vertical);
/*
* Ищем наибольшую ненулевую последовательность.
* Индексы границ последовательности и будут граничными точками поля.
*/
int[] vParam = getParamsFromSequence(horizontal);
int[] hParam = getParamsFromSequence(vertical);
int outX = hParam[0];
int outY = vParam[0];
int outWidth = hParam[1];
int outHeight = vParam[1];
// Подсчет размера ячейки
cellSize = Math.max((outWidth / boardSize), (outHeight / boardSize));
return new Point(outX, outY);
}
/**
* Фильтр последовательности от малозначимых значений.
* @param source последовательность вхождений цвета
* @return отфильтрованный массив со значениями 0 и 1
*/
private int[] filterByMean(int[] source) {
long mean = 0;
for (int i = 0; i < source.length; i++) {
mean += source[i];
}
mean = mean / source.length;
for (int i = 0; i < source.length; i++) {
source[i] = (source[i] > mean) ? 1 : 0;
}
return source;
}
/**
* Поиск самой длинной последовательности в массиве.
* @param source входная последовательность из нулей и единиц
* @return массив параметров - индекс начала последовательности и её длина
*/
private int[] getParamsFromSequence(int[] source) {
int maxStart = 0, start = 0;
int maxLength = 0, length = 0;
for (int i = 1; i < source.length; i++) {
if (source[i] == 0) {
start = 0;
length = 0;
continue;
}
if (source[i] == source[i-1]) {
length++;
if (maxLength < length) {
maxStart = start;
maxLength = length;
}
} else {
// Если предыдущий элемент был нулевым - начинаем новую последовательность
start = i;
}
}
return new int[] {maxStart, maxLength};
}
/**
* Поиск координаты кнопки с цветом template
* @param img изображение, на котором будем искать
* @param template шаблон цвета
* @return координата X. Y, или null если не нашли
*/
private Point findButton(BufferedImage img, int template) {
int h2 = img.getHeight() / 2;
// Искать будем с середины по вертикали, так быстрее найдём
for (int y = 0; y < h2; y++) {
for (int x = 0; x < img.getWidth(); x++) {
int color = img.getRGB(x, h2 - y);
if (isEquals(color, template, FIND_BUTTON_TOLERANCE)) {
return new Point(x, h2 - y);
}
color = img.getRGB(x, h2 + y);
if (isEquals(color, template, FIND_BUTTON_TOLERANCE)) {
return new Point(x, h2 + y);
}
}
}
// Не нашли
return null;
}
/**
* Проверка на соответствие цветов друг другу
* @param color1 первый цвет
* @param color2 второй цвет
* @param tolerance чувствительность
* @return true - соответствуют, false - нет
*/
private boolean isEquals(int color1, int color2, int tolerance) {
if (tolerance < 2) return color1 == color2;
int r1 = (color1 >> 16) & 0xff;
int g1 = (color1 >> 8) & 0xff;
int b1 = color1 & 0xff;
int r2 = (color2 >> 16) & 0xff;
int g2 = (color2 >> 8) & 0xff;
int b2 = color2 & 0xff;
return (Math.abs(r1 - r2) <= tolerance) &&
(Math.abs(g1 - g2) <= tolerance) &&
(Math.abs(b1 - b2) <= tolerance);
}
/**
* Получение яркости цвета
* @param color исходный цвет
* @return яркость (0..255)
*/
private int getBrightness(int color) {
int qr = (color >> 16) & 0xff;
int qg = (color >> 8) & 0xff;
int qb = color & 0xff;
return (qr + qg + qb) / 3;
}
/*
* Получение цвета из монохромного изображения.
* return true - белый, false - чёрный
*/
private boolean getBWPixel(int x, int y) {
if ((x < 0) || (y < 0) || (x >= w) || (y >= h)) return false;
return monochrome[y * w + x];
}
}