Created
April 18, 2019 06:53
-
-
Save KableM/d0aea2d0cd902c6bc6a24acd90c22b68 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import javax.imageio.ImageIO; | |
import javax.swing.*; | |
import java.awt.*; | |
import java.awt.geom.Arc2D; | |
import java.awt.geom.Ellipse2D; | |
import java.awt.geom.Point2D; | |
import java.awt.image.BufferedImage; | |
import java.io.File; | |
import java.io.IOException; | |
import java.util.ArrayList; | |
import java.util.Arrays; | |
import java.util.List; | |
import java.util.Random; | |
/** | |
* Run on the command line with: | |
* javac Tires.java | |
* java Tires | |
* | |
* @author @kable_codes | |
*/ | |
public class Tires { | |
private final BufferedImage inputImage; | |
private final List<Tire> tires; | |
private final Color black = new Color(0x000000); | |
// 2018 tire colours | |
// private final List<Color> tireColors = Arrays.asList(new Color(0xEDC4F5), | |
// new Color(0xB32AF5), | |
// new Color(0xFFFFFF), | |
// new Color(0xFFE000), | |
// new Color(0xFF0700), | |
// new Color(0x1285FF), | |
// new Color(0xFF8503), | |
// black); | |
// 2019 tire colors | |
private final List<Color> tireColors = Arrays.asList(new Color(0xFFE000), | |
new Color(0xFFFFFF), | |
new Color(0xEE0700), | |
black); | |
private final int stageWidth; | |
private final int stageHeight; | |
private Point2D.Float cameraPosition; | |
private float cameraZoom = 80; | |
public Tires(File file) throws IOException { | |
inputImage = ImageIO.read(file); | |
this.stageWidth = inputImage.getWidth(); | |
this.stageHeight = inputImage.getHeight(); | |
cameraPosition = new Point2D.Float(stageWidth / 2f, stageHeight / 2f); | |
tires = new ArrayList<>(); | |
float tireRadius = 3; | |
Random random = new Random(); | |
for (int i = 0; i < 50000; i++) { | |
float x = random.nextFloat() * stageWidth; | |
float y = random.nextFloat() * stageHeight; | |
Color color = getColor((int) x, (int) y); | |
Color tireColor = getClosestTireColor(color); | |
if (tireColor != null) { | |
tires.add(new Tire(x, y, tireRadius, tireColor)); | |
} | |
} | |
} | |
private Color getClosestTireColor(Color color) { | |
Color closestColor = null; | |
Double closestDisance = null; | |
for (Color compare : tireColors) { | |
double distance = colorDistance(color, compare); | |
if (closestDisance == null || distance < closestDisance) { | |
closestColor = compare; | |
closestDisance = distance; | |
} | |
} | |
// if (closestDisance > 35000) { | |
// return null; | |
// } | |
if (closestColor == black) { | |
return null; | |
} | |
return closestColor; | |
} | |
private Color getColor(int x, int y) { | |
if (x < 0 || x >= inputImage.getWidth() || y < 0 || y >= inputImage.getHeight()) { | |
return new Color(0x000000); | |
} | |
int rgb = inputImage.getRGB(x, y); | |
return new Color(rgb); | |
} | |
private double colorDistance(Color c1, Color c2) { | |
int rd = c2.getRed() - c1.getRed(); | |
int gd = c2.getGreen() - c1.getGreen(); | |
int bd = c2.getBlue() - c1.getBlue(); | |
return rd * rd + gd * gd + bd * bd; | |
} | |
protected void step(float dt) { | |
cameraZoom *= (1 - dt * 0.0004); | |
if (cameraZoom < 1) { | |
cameraZoom = 1; | |
} | |
tires.forEach(tire -> tire.rotation += dt * tire.rSpeed); | |
} | |
protected void draw(Graphics2D graphics) { | |
graphics.setColor(new Color(0x111111)); | |
graphics.fillRect(0, 0, stageWidth, stageHeight); | |
tires.forEach(tire -> tire.draw(graphics)); | |
} | |
class Tire { | |
private float x; | |
private float y; | |
private float radius; | |
private float rSpeed = -0.9f; | |
private float rotation; | |
private Color color; | |
private final Color tireBase = new Color(0x222222); | |
private final Color wheelColor = new Color(0x888888); | |
private final Color wheelColor2 = new Color(0x666666); | |
public Tire(float x, float y, float radius, Color color) { | |
this.x = x; | |
this.y = y; | |
this.radius = radius; | |
this.color = color; | |
} | |
public void draw(Graphics2D graphics) { | |
float renderX = (x - stageWidth / 2f) * (cameraZoom - 1) + stageWidth / 2f - cameraPosition.x + x; | |
float renderY = (y - stageHeight / 2f) * (cameraZoom - 1) + stageHeight / 2f - cameraPosition.y + y; | |
float renderRadius = radius * cameraZoom; | |
if (renderX + renderRadius < 0 || | |
renderX - renderRadius > stageWidth || | |
renderY + renderRadius < 0 || | |
renderY - renderRadius > stageHeight) { | |
return; | |
} | |
Ellipse2D base = new Ellipse2D.Float(renderX - renderRadius, renderY - renderRadius, renderRadius * 2, renderRadius * 2); | |
RadialGradientPaint basePaint = new RadialGradientPaint(renderX, renderY, renderRadius, | |
new float[]{0.5f, 0.8f, 1f}, | |
new Color[]{black, tireBase, black}); | |
graphics.setPaint(basePaint); | |
graphics.fill(base); | |
float markRadius = renderRadius * 0.8f; | |
Arc2D.Float arc1 = new Arc2D.Float(renderX - markRadius, renderY - markRadius, markRadius * 2, markRadius * 2, rotation - 45, 100, Arc2D.OPEN); | |
Arc2D.Float arc2 = new Arc2D.Float(renderX - markRadius, renderY - markRadius, markRadius * 2, markRadius * 2, rotation + 135, 100, Arc2D.OPEN); | |
graphics.setColor(this.color); | |
graphics.setStroke(new BasicStroke(renderRadius * 0.1f, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_ROUND)); | |
graphics.draw(arc1); | |
graphics.draw(arc2); | |
float wheelRadius = renderRadius * 0.6f; | |
Ellipse2D wheel = new Ellipse2D.Float(renderX - wheelRadius, renderY - wheelRadius, wheelRadius * 2, wheelRadius * 2); | |
RadialGradientPaint wheelPaint = new RadialGradientPaint(renderX, renderY, wheelRadius, | |
new float[]{0.1f, 0.15f, 0.2f, 0.25f, 0.8f, 0.85f}, | |
new Color[]{wheelColor, black, wheelColor, black, black, wheelColor2}); | |
graphics.setPaint(wheelPaint); | |
graphics.fill(wheel); | |
} | |
} | |
public static void main(String[] args) throws IOException { | |
String path = "/path/to/image"; | |
if ("/path/to/image".equals(path)) { | |
throw new RuntimeException("Please set a path for the image"); | |
} | |
Tires tires = new Tires(new File(path)); | |
Preview preview = new Preview("Tires", tires, tires.stageWidth, tires.stageHeight, 30, true); | |
preview.start(); | |
} | |
static class Preview extends JFrame { | |
private final Tires tires; | |
private final int width; | |
private final int height; | |
private Timer timer; | |
public Preview(String title, | |
Tires tires, | |
int width, | |
int height, | |
int frameRate, | |
boolean anitalias) throws HeadlessException { | |
super(title); | |
this.width = width; | |
this.height = height; | |
this.tires = tires; | |
float delay = 1000f / frameRate; | |
timer = new Timer((int) delay, e -> step(delay)); | |
JPanel container = new JPanel() { | |
@Override | |
protected void paintComponent(Graphics g) { | |
if (anitalias) { | |
RenderingHints hints = new RenderingHints(null); | |
hints.put(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); | |
hints.put(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); | |
((Graphics2D) g).setRenderingHints(hints); | |
} | |
tires.draw((Graphics2D) g); | |
} | |
}; | |
getContentPane().add(BorderLayout.CENTER, container); | |
} | |
public void start() { | |
EventQueue.invokeLater(() -> { | |
setLocationRelativeTo(null); | |
setDefaultCloseOperation(EXIT_ON_CLOSE); | |
timer.start(); | |
setVisible(true); | |
setPreferredSize(new Dimension(width, height + getInsets().top)); | |
pack(); | |
}); | |
} | |
private void step(float dt) { | |
tires.step(dt); | |
repaint(); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment