Для одной задумки понадобилась библиотека, для работы с bmp изображениями. Найти ее было не сложно. Сейчас чего-только не написали уже для Java. Спасибо Nayuki Minase за то, что он потрудился и написал библиотеку для работы с bmp.
И вот я конечно же ищу сэмплы, чтобы разобраться как сие диво работает. И что вижу? Одна из демок рисует Множество Мандельброта. Ну вот я и завис, уж простите. В прошлом, когда еще кодил на Delphi я тоже завис - на по-дольше. В результате родилась такой вот фрактальный браузер. Сегодня я решил помянуть это и написать свою версию рисовалки фракталов на основе библиотеки работы с bmp рисунками.
Вот один из рисунков в большом разрешении (клик по рисунку увеличит его, но осторожно - там 20 мег)...
А теперь о реализации (исходник можно скачать тут (зеркало), осторожно внутри исходники библиотеки по работе с BMP, она распостраняется по MIT лицензии).
Итак интерфейсик Фрактала
Код причесал как мог - он был портирован из Delphi. Я его писал лет 15 назад - можешь представить, что там было...
И вот я конечно же ищу сэмплы, чтобы разобраться как сие диво работает. И что вижу? Одна из демок рисует Множество Мандельброта. Ну вот я и завис, уж простите. В прошлом, когда еще кодил на Delphi я тоже завис - на по-дольше. В результате родилась такой вот фрактальный браузер. Сегодня я решил помянуть это и написать свою версию рисовалки фракталов на основе библиотеки работы с bmp рисунками.
Вот один из рисунков в большом разрешении (клик по рисунку увеличит его, но осторожно - там 20 мег)...
Итак интерфейсик Фрактала
public interface Fractal { Position getZoom(); int getFunction(double x, double y, int iterations); }Вот Мандельброт
public class Mandelbrot implements Fractal { private Position mandelbrot = new Position(-1.9, 0.5, -1.2, 1.2); @Override public Position getZoom() { return mandelbrot.zoom(40).move(-1.25, 2).zoom(5); } @Override public int getFunction(double a, double b, int iterations) { double r = 0; double x = 0; double y = 0; int color = iterations; while (color > 0 && r < 4) { double x2 = x * x; double y2 = y * y; double xy = x * y; x = x2 - y2 + a; y = 2 * xy + b; r = x2 + y2; color--; } return color; } }Вот Джулия
public class Julia implements Fractal { @Override public Position getZoom() { return new Position(-1.59, 1.527, -1.558, 1.558); } @Override public int getFunction(double x0, double y0, int iterations) { double r = 0; double a = -0.55; double b = -0.55; double x = x0; double y = y0; int color = iterations; while (color > 0 && r < 4) { double x2 = x * x; double y2 = y * y; double xy = x * y; x = x2 - y2 + a; y = 2 * xy + b; r = x2 + y2; color--; } return color; } }Класс Position я сделал для удобной работы с координатами (зум, мув). Простите за французский в названии полей/переменных.
import p79068.bmpio.Rgb888Image; public class Position { private double bxMin; private double bxMax; private double byMin; private double byMax; Position(double bxMin, double bxMax, double byMin, double byMax) { this.bxMin = bxMin; this.bxMax = bxMax; this.byMin = byMin; this.byMax = byMax; } public Position zoom(double zoom) { double lx = (bxMax - bxMin)*(zoom-1)/(2*zoom); double ly = (byMax - byMin)*(zoom-1)/(2*zoom); double xMin = bxMin + lx; double xMax = bxMax - lx; double yMin = byMin + ly; double yMax = byMax - ly; return new Position(xMin, xMax, yMin, yMax); } public Position move(double dx, double dy) { double xMin = bxMin + (bxMax - bxMin)*dx; double xMax = bxMax + (bxMax - bxMin)*dx; double yMin = byMin + (byMax - byMin)*dy; double yMax = byMax + (byMax - byMin)*dy; return new Position(xMin, xMax, yMin, yMax); } public Position resize(double width, double height) { if (width/height > 1) { double dx = (bxMax - bxMin)*(width/height - 1)/2; double xMin = bxMin - dx; double xMax = bxMax + dx; double yMin = byMin; double yMax = byMax; return new Position(xMin, xMax, yMin, yMax); } if (width/height < 1) { double dy = (byMax - byMin)*(height/width - 1)/2; double xMin = bxMin; double xMax = bxMax; double yMin = byMin - dy; double yMax = byMax + dy; return new Position(xMin, xMax, yMin, yMax); } else { return this; } } public double getXMin() { return bxMin; } public double getXMax() { return bxMax; } public double getYMin() { return byMin; } public double getYMax() { return byMax; } public String toString() { return String.format("x=[%s...%s]\ny=[%s...%s]", bxMin, bxMax, byMin, byMax); } }Фрактальное изображение, основано на интерфейсе из библиотеки. Суть в том, что метод getRgb888Pixel() будет дергаться либой по каждому пикселю изобраения шириной и высотой getWidth() на getHeight()..
import p79068.bmpio.Rgb888Image; public class FractalImage implements Rgb888Image { private int width; private int height; private Fractal fractal; private Palette palette; private Position zoom; public FractalImage(int width, int height, Fractal fractal, Palette palette) { this.width = width; this.height = height; this.fractal = fractal; this.palette = palette; zoom = fractal.getZoom().resize(width, height); } @Override public int getWidth() { return width; } @Override public int getHeight() { return height; } @Override public int getRgb888Pixel(int x, int y) { double x1 = zoom.getXMin() + (x + 0.5) / width * (zoom.getXMax() - zoom.getXMin()); double y1 = zoom.getYMax() - (y + 0.5) / height * (zoom.getYMax() - zoom.getYMin()); int r = fractal.getFunction(x1, y1, palette.getSize() - 1); return palette.getColor(r); } public String getFractalName() { return fractal.getClass().getSimpleName(); } }Он зависим не только от фрактала, но и от палитры
public interface Palette { int getColor(int index); int getSize(); }Реализация которой в чернобелом варианте будет такой. Всего в палитре этой 256 цветов - 8 бит на канал (red=green=blue).
public class BlackAndWhite256Palette implements Palette { @Override public int getColor(int r) { return (r) | (r << 8) | (r << 16); } @Override public int getSize() { return 256; } }А вот палитра цветная рендомная, она рисует более интересные фракталы.
public class RandomPalette implements Palette { class Marker { int x; int color; public Marker(int x, int color) { this.x = x; this.color = color; } } private static final int MW = 8; // ширина маркера private static final int[] colorArray = new int[]{ 0xFFFFFF, 0x00FFFF, 0xFF00FF, 0xFFFF00, 0x0000FF, 0xFF0000, 0x00FF00, 0xC0FFFF, 0xFFC0FF, 0xFFFFC0, 0xC0C0FF, 0xFFC0C0, 0xC0FFC0, 0xC000FF, 0x00C0FF, 0x00FFC0, 0xC0FF00, 0xFFC000, 0xFF00C0}; private List<Marker> markers = new LinkedList<Marker>(); private int[] palette; public RandomPalette(int size) { palette = new int[size]; int color = getRandomColor(); markers.add(new Marker(0, color)); markers.add(new Marker(size, color)); int count = random(size / (MW * 3)) + 3; double r = size / (count + 1); addBlackMarker(MW + 1); for (int i = 1; i <= count; i++) { int j = (int) (i * r); if (yesOrNo()) { addBlackMarker(j - MW - 1); } addMarker(j, getRandomColor()); if (yesOrNo()) { addBlackMarker(j + MW + 1); } } addBlackMarker(size - MW - 1); calculatePalette(); } private int getRandomColor() { return colorArray[random(colorArray.length)]; } private boolean yesOrNo() { return random(2) == 1; } private void addBlackMarker(int x) { addMarker(x, 0); } private void calculatePalette() { int x = markers.get(0).x; for (int i = 0; i < markers.size() - 1; i++) { int length = markers.get(i + 1).x - markers.get(i).x; for (int dx = 0; dx < length; dx++) { palette[x + dx] = colorChange(markers.get(i).color, markers.get(i + 1).color, length, dx); } x = x + length; } palette[0] = 0; } private int colorChange(int from, int to, double len, double x) { double red = change(getR(from), getR(to), len, x); double green = change(getG(from), getG(to), len, x); double blue = change(getB(from), getB(to), len, x); return rgb((int) red, (int) green, (int) blue); } private double change(double from, double to, double len, double x) { if (from == to) { return from; } double delta = Math.abs(from - to) * x / len; if (from < to) { return from + delta; } else { return from - delta; } } private int getR(int col) { return (col & 0x0000FF); } private int getG(int col) { return (col & 0x00FF00) >>> 8; } private int getB(int col) { return (col & 0xFF0000) >>> 16; } private int rgb(int r, int g, int b) { return (r) | (g << 8) | (b << 16); } private void addMarker(int x, int color) { // определяем после которого маркера будет создаваемый int index; for (index = 0; index < markers.size(); index++) { if ((markers.get(index).x < x) && (x < markers.get(index + 1).x)) { break; } } // если после последнего то выходим if (index == markers.size()) { return; } // если маркер некуда втиснуть между двумя ближайшими то выходим if (markers.get(index + 1).x - markers.get(index).x < MW + 2) { return; } // очень близко ставить маркер возле соседнего нельзя if ((markers.get(index).x + MW + 1 > x) || (markers.get(index + 1).x - MW - 1 < x)) { return; } markers.add(index + 1, new Marker(x, color)); } private int random(int n) { return new Random().nextInt(n); } @Override public int getColor(int r) { return palette[r]; } @Override public int getSize() { return palette.length; } }Небольшой консольный прогресбарчик-декоратор для того, чтобы я знал как долго будет рисоваться фраклат размером 20000x20000 пикселей. Я его прооптимизировал немного, чтобы он не занимался делением каждый пиксель.
public class Progress implements Rgb888Image { private long square; private long count; private long iteration; private long next; private FractalImage image; public Progress(FractalImage image) { this.image = image; this.square = getWidth()*getHeight(); this.next = square/100; } @Override public int getWidth() { return image.getWidth(); } @Override public int getHeight() { return image.getHeight(); } @Override public int getRgb888Pixel(int x, int y) { calculateProgress(); return image.getRgb888Pixel(x, y); } private void calculateProgress() { iteration++; if (iteration == next) { count = count + iteration; iteration = 0; int progress = (int) ((double)count*100 / square); System.out.println(progress + "%"); } } public String getFractalName() { return image.getFractalName(); } }Ну и Main метод
import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import p79068.bmpio.BmpImage; import p79068.bmpio.BmpWriter; public final class FractalDemo { public static void main(String[] args) throws IOException { draw(new Mandelbrot()); draw(new Julia()); } private static void draw(Fractal fractal) throws IOException { BmpImage bmp = new BmpImage(); Palette palette = new BlackAndWhite256Palette(); Progress image = new Progress(new FractalImage(1920, 1080, fractal, palette)); bmp.image = image; File file = new File(image.getFractalName() + ".bmp"); FileOutputStream out = new FileOutputStream(file); BmpWriter.write(out, bmp); out.close(); } }Уверен кто-то это полюбит так же как и я...
О, такі рачі завжди мене надихають :)
ОтветитьУдалитьБезконечная история....
УдалитьСтатья и исходники обновились - добалено понятие Палитра, которой рисуется фрактал. Есть два типа палитры ЧБ и рендомная цветная.
УдалитьДобрый день!
ОтветитьУдалитьМожете, пожалуйста, перезалить исходный код?
Добрый день Степан. Обновил ссылки в статье, выложил на github https://github.com/codenjoyme/fractal
УдалитьПрошу поделиться наработками, если планируете развивать