/* * Copyright 2017 Laszlo Balazs-Csiki * * This file is part of Pixelitor. Pixelitor is free software: you * can redistribute it and/or modify it under the terms of the GNU * General Public License, version 3 as published by the Free * Software Foundation. * * Pixelitor is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Pixelitor. If not, see <http://www.gnu.org/licenses/>. */ package pixelitor.filters; import com.jhlabs.image.Colormap; import com.jhlabs.image.PointFilter; import pixelitor.filters.gui.AngleParam; import pixelitor.filters.gui.BooleanParam; import pixelitor.filters.gui.GradientParam; import pixelitor.filters.gui.GroupedRangeParam; import pixelitor.filters.gui.IntChoiceParam; import pixelitor.filters.gui.IntChoiceParam.Value; import pixelitor.filters.gui.ParamSet; import pixelitor.filters.gui.RangeParam; import pixelitor.filters.gui.ReseedNoiseFilterAction; import pixelitor.filters.gui.ShowOriginal; import java.awt.Color; import java.awt.image.BufferedImage; import static com.jhlabs.image.WaveType.wave; import static com.jhlabs.math.Noise.noise2; import static com.jhlabs.math.Noise.turbulence2; import static com.jhlabs.math.Noise.turbulence2B; import static net.jafama.FastMath.atan2; import static net.jafama.FastMath.cos; import static net.jafama.FastMath.pow; import static net.jafama.FastMath.sin; import static net.jafama.FastMath.sqrt; /** * Marble filter */ public class Marble extends FilterWithParametrizedGUI { public static final String NAME = "Marble"; private final RangeParam zoom = new RangeParam("Zoom", 1, 10, 200); private final AngleParam angle = new AngleParam("Angle", 0); private final RangeParam distortion = new RangeParam("Distortion", 0, 25, 100); private final RangeParam detailsLevel = new RangeParam("Level", 0, 3, 8); private final RangeParam detailsStrength = new RangeParam("Strength", 0, 12, 50); private final IntChoiceParam type = new IntChoiceParam("Type", new Value[]{ new Value("Lines", Impl.TYPE_LINES), new Value("Rings", Impl.TYPE_RINGS), new Value("Grid", Impl.TYPE_GRID), new Value("Star", Impl.TYPE_STAR), }); private final IntChoiceParam waveType = new IntChoiceParam("Wave Type", IntChoiceParam.waveTypeChoices); private final BooleanParam smoothDetails = new BooleanParam("Smoother Details", false); private final GradientParam gradient = new GradientParam("Colors", new float[]{0.0f, 0.5f, 1.0f}, new Color[]{ new Color(1, 14, 5), new Color(20, 50, 38), new Color(235, 255, 251), }); private Impl filter; public Marble() { super(ShowOriginal.NO); GroupedRangeParam details = new GroupedRangeParam("Details", new RangeParam[]{detailsLevel, detailsStrength}, false); setParamSet(new ParamSet( type, waveType, angle, zoom.withAdjustedRange(0.25), distortion, details.setLinkable(false), smoothDetails, gradient ).withAction(new ReseedNoiseFilterAction())); } @Override public BufferedImage doTransform(BufferedImage src, BufferedImage dest) { if (filter == null) { filter = new Impl(NAME); } filter.setType(type.getValue()); filter.setWaveType(waveType.getValue()); double angleShift = Math.PI / 2; if (type.getValue() == Impl.TYPE_GRID) { angleShift = Math.PI / 4; } filter.setAngle((float) (angle.getValueInRadians() + angleShift)); filter.setZoom(zoom.getValueAsFloat()); filter.setStrength(distortion.getValueAsFloat() / 5.0f); filter.setDetails(detailsLevel.getValueAsFloat()); filter.setDetailsStrength(detailsStrength.getValueAsFloat() / 4.0f); filter.setColormap(gradient.getValue()); filter.setSmoothDetails(smoothDetails.isChecked()); dest = filter.filter(src, dest); return dest; } private static class Impl extends PointFilter { private static final int TYPE_LINES = 1; private static final int TYPE_GRID = 2; private static final int TYPE_RINGS = 3; private static final int TYPE_STAR = 4; private float m00, m01, m10, m11; private float rotAngle; private float zoom = 200; private float detailsStrength; private float strength; private float octaves; private int type; private Colormap colormap; private float cx, cy; private int waveType; private boolean smoothDetails; protected Impl(String filterName) { super(filterName); } public void setDetailsStrength(float f) { this.detailsStrength = f; } public void setStrength(float f) { this.strength = f; } public void setDetails(float f) { octaves = (float) pow(2.0, f - 1.0); } public void setSmoothDetails(boolean smoothDetails) { this.smoothDetails = smoothDetails; } public void setType(int type) { this.type = type; } public void setWaveType(int waveType) { this.waveType = waveType; } public void setAngle(float angle) { this.rotAngle = angle; float cos = (float) cos(angle); float sin = (float) sin(angle); m00 = cos; m01 = sin; m10 = -sin; m11 = cos; } public void setZoom(float zoom) { this.zoom = zoom; } @Override public BufferedImage filter(BufferedImage src, BufferedImage dst) { cx = src.getWidth() / 2.0f; cy = src.getHeight() / 2.0f; return super.filter(src, dst); } @Override public int filterRGB(int x, int y, int rgb) { double dy = y - cy; double dx = x - cx; float nx = (float) (m00 * dx + m01 * dy); float ny = (float) (m10 * dx + m11 * dy); nx /= zoom; ny /= zoom; float c; float f = strength * (noise2(nx * 0.1f, ny * 0.1f)); if (smoothDetails) { f += detailsStrength * turbulence2B(nx * 0.2f, ny * 0.2f, octaves); } else { f += detailsStrength * turbulence2(nx * 0.2f, ny * 0.2f, octaves); } switch (type) { case TYPE_LINES: c = (float) ((1 + wave((nx + f), waveType)) / 2); break; case TYPE_GRID: float f2 = strength * (noise2(ny * -0.1f, nx * -0.1f)); if (smoothDetails) { f2 += detailsStrength * turbulence2B(ny * -0.2f, nx * -0.2f, octaves); } else { f2 += detailsStrength * turbulence2(ny * -0.2f, nx * -0.2f, octaves); } c = ((float) (2.0f + wave(nx + f, waveType) + wave(ny + f2, waveType))) / 4.0f; break; case TYPE_RINGS: double dist = sqrt(dx * dx + dy * dy) / zoom; f += dist; c = (float) ((1 + wave(f, waveType)) / 2); break; case TYPE_STAR: double pixelAngle = atan2(dy, dx); f += (pixelAngle - rotAngle) * 10.0f; c = (float) ((1 + wave(f, waveType)) / 2); break; default: throw new IllegalStateException(); } return colormap.getColor(c); } public void setColormap(Colormap colormap) { this.colormap = colormap; } } }