feat: Refactor visualization to generate BMP files instead of SVG
Signed-off-by: szdytom <szdytom@qq.com>
This commit is contained in:
parent
2b5f62da08
commit
4d88ea4a29
@ -1,50 +1,39 @@
|
||||
#include "bmp.h"
|
||||
#include "generation.h"
|
||||
#include "tile.h"
|
||||
#include "tilemap.h"
|
||||
#include <cstdlib>
|
||||
#include <fstream>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
// Color mapping for different base tile types
|
||||
const char *get_tile_color(istd::BaseTileType type) {
|
||||
// Get BMP color for different base tile types
|
||||
BmpColors::Color get_tile_color(istd::BaseTileType type) {
|
||||
switch (type) {
|
||||
case istd::BaseTileType::Land:
|
||||
return "#90EE90"; // Light green
|
||||
return BmpColors::LAND;
|
||||
case istd::BaseTileType::Mountain:
|
||||
return "#8B4513"; // Saddle brown
|
||||
return BmpColors::MOUNTAIN;
|
||||
case istd::BaseTileType::Sand:
|
||||
return "#F4A460"; // Sandy brown
|
||||
return BmpColors::SAND;
|
||||
case istd::BaseTileType::Water:
|
||||
return "#1E90FF"; // Dodger blue
|
||||
return BmpColors::WATER;
|
||||
case istd::BaseTileType::Ice:
|
||||
return "#B0E0E6"; // Powder blue
|
||||
return BmpColors::ICE;
|
||||
default:
|
||||
return "#808080"; // Gray for unknown types
|
||||
return BmpColors::Color(128, 128, 128); // Gray for unknown types
|
||||
}
|
||||
}
|
||||
|
||||
// Generate SVG file from tilemap
|
||||
void generate_svg(const istd::TileMap &tilemap, const std::string &filename) {
|
||||
std::ofstream file(filename);
|
||||
if (!file.is_open()) {
|
||||
std::cerr << "Error: Could not open output file: " << filename
|
||||
<< std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
// Generate BMP file from tilemap
|
||||
void generate_bmp(const istd::TileMap &tilemap, const std::string &filename) {
|
||||
const int chunks_per_side = tilemap.get_size();
|
||||
const int tiles_per_chunk = istd::Chunk::size;
|
||||
const int total_tiles = chunks_per_side * tiles_per_chunk;
|
||||
const int tile_size = 2; // Size of each tile in SVG pixels
|
||||
const int svg_size = total_tiles * tile_size;
|
||||
const int tile_size = 2; // Size of each tile in pixels
|
||||
const int image_size = total_tiles * tile_size;
|
||||
|
||||
// SVG header
|
||||
file << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
|
||||
file << "<svg width=\"" << svg_size << "\" height=\"" << svg_size
|
||||
<< "\" xmlns=\"http://www.w3.org/2000/svg\">\n";
|
||||
file << "<title>Tilemap Visualization</title>\n";
|
||||
BmpWriter bmp(image_size, image_size);
|
||||
|
||||
// Generate tiles
|
||||
for (int chunk_y = 0; chunk_y < chunks_per_side; ++chunk_y) {
|
||||
@ -58,64 +47,51 @@ void generate_svg(const istd::TileMap &tilemap, const std::string &filename) {
|
||||
int global_x = chunk_x * tiles_per_chunk + tile_x;
|
||||
int global_y = chunk_y * tiles_per_chunk + tile_y;
|
||||
|
||||
int svg_x = global_x * tile_size;
|
||||
int svg_y = global_y * tile_size;
|
||||
auto color = get_tile_color(tile.base);
|
||||
|
||||
const char *color = get_tile_color(tile.base);
|
||||
|
||||
file << "<rect x=\"" << svg_x << "\" y=\"" << svg_y
|
||||
<< "\" width=\"" << tile_size << "\" height=\""
|
||||
<< tile_size << "\" fill=\"" << color << "\"/>\n";
|
||||
// Draw a tile_size x tile_size block
|
||||
for (int dy = 0; dy < tile_size; ++dy) {
|
||||
for (int dx = 0; dx < tile_size; ++dx) {
|
||||
int pixel_x = global_x * tile_size + dx;
|
||||
int pixel_y = global_y * tile_size + dy;
|
||||
bmp.set_pixel(
|
||||
pixel_x, pixel_y, color.r, color.g, color.b
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add grid lines for chunk boundaries
|
||||
file << "<!-- Chunk boundaries -->\n";
|
||||
// Add chunk boundary lines (optional - makes file larger)
|
||||
/*
|
||||
for (int i = 0; i <= chunks_per_side; ++i) {
|
||||
int pos = i * tiles_per_chunk * tile_size;
|
||||
// Vertical lines
|
||||
file << "<line x1=\"" << pos << "\" y1=\"0\" x2=\"" << pos << "\" y2=\""
|
||||
<< svg_size << "\" stroke=\"black\" stroke-width=\"2\"/>\n";
|
||||
// Horizontal lines
|
||||
file << "<line x1=\"0\" y1=\"" << pos << "\" x2=\"" << svg_size
|
||||
<< "\" y2=\"" << pos
|
||||
<< "\" stroke=\"black\" stroke-width=\"2\"/>\n";
|
||||
int pos = i * tiles_per_chunk * tile_size;
|
||||
// Vertical lines
|
||||
for (int y = 0; y < image_size; ++y) {
|
||||
if (pos < image_size) {
|
||||
bmp.set_pixel(pos, y, 0, 0, 0); // Black
|
||||
}
|
||||
}
|
||||
// Horizontal lines
|
||||
for (int x = 0; x < image_size; ++x) {
|
||||
if (pos < image_size) {
|
||||
bmp.set_pixel(x, pos, 0, 0, 0); // Black
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
if (!bmp.save(filename)) {
|
||||
std::cerr << "Error: Could not save BMP file: " << filename
|
||||
<< std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
// Legend
|
||||
file << "<!-- Legend -->\n";
|
||||
file << "<g transform=\"translate(10, 10)\">\n";
|
||||
file << "<rect x=\"0\" y=\"0\" width=\"200\" height=\"140\" fill=\"white\" "
|
||||
"stroke=\"black\" stroke-width=\"1\" opacity=\"0.9\"/>\n";
|
||||
file << "<text x=\"10\" y=\"20\" font-family=\"Arial\" font-size=\"14\" "
|
||||
"font-weight=\"bold\">Legend</text>\n";
|
||||
|
||||
const std::pair<istd::BaseTileType, const char *> legend_items[] = {
|
||||
{istd::BaseTileType::Land, "Land" },
|
||||
{istd::BaseTileType::Mountain, "Mountain"},
|
||||
{istd::BaseTileType::Sand, "Sand" },
|
||||
{istd::BaseTileType::Water, "Water" },
|
||||
{istd::BaseTileType::Ice, "Ice" }
|
||||
};
|
||||
|
||||
for (int i = 0; i < 5; ++i) {
|
||||
int y_pos = 40 + i * 20;
|
||||
file << "<rect x=\"10\" y=\"" << (y_pos - 10)
|
||||
<< "\" width=\"15\" height=\"15\" fill=\""
|
||||
<< get_tile_color(legend_items[i].first)
|
||||
<< "\" stroke=\"black\" stroke-width=\"1\"/>\n";
|
||||
file << "<text x=\"30\" y=\"" << y_pos
|
||||
<< "\" font-family=\"Arial\" font-size=\"12\">"
|
||||
<< legend_items[i].second << "</text>\n";
|
||||
}
|
||||
file << "</g>\n";
|
||||
|
||||
file << "</svg>\n";
|
||||
file.close();
|
||||
|
||||
std::cout << "SVG file generated: " << filename << std::endl;
|
||||
std::cout << "BMP file generated: " << filename << std::endl;
|
||||
std::cout << "Image size: " << image_size << "x" << image_size << " pixels"
|
||||
<< std::endl;
|
||||
std::cout << "Tilemap size: " << total_tiles << "x" << total_tiles
|
||||
<< " tiles" << std::endl;
|
||||
std::cout << "Chunks: " << chunks_per_side << "x" << chunks_per_side
|
||||
@ -159,8 +135,8 @@ void print_statistics(const istd::TileMap &tilemap) {
|
||||
int main(int argc, char *argv[]) {
|
||||
// Parse command line arguments
|
||||
if (argc != 3) {
|
||||
std::cerr << "Usage: " << argv[0] << " <seed> <output_file.svg>\n";
|
||||
std::cerr << "Example: " << argv[0] << " 12345 output.svg\n";
|
||||
std::cerr << "Usage: " << argv[0] << " <seed> <output_file.bmp>\n";
|
||||
std::cerr << "Example: " << argv[0] << " 12345 output.bmp\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
@ -169,8 +145,8 @@ int main(int argc, char *argv[]) {
|
||||
|
||||
// Validate output filename
|
||||
if (output_filename.length() < 4
|
||||
|| output_filename.substr(output_filename.length() - 4) != ".svg") {
|
||||
std::cerr << "Error: Output filename must end with .svg\n";
|
||||
|| output_filename.substr(output_filename.length() - 4) != ".bmp") {
|
||||
std::cerr << "Error: Output filename must end with .bmp\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
@ -191,9 +167,9 @@ int main(int argc, char *argv[]) {
|
||||
std::cout << "Generating terrain..." << std::endl;
|
||||
istd::map_generate(tilemap, config);
|
||||
|
||||
// Generate SVG output
|
||||
std::cout << "Creating SVG visualization..." << std::endl;
|
||||
generate_svg(tilemap, output_filename);
|
||||
// Generate BMP output
|
||||
std::cout << "Creating BMP visualization..." << std::endl;
|
||||
generate_bmp(tilemap, output_filename);
|
||||
|
||||
// Print statistics
|
||||
print_statistics(tilemap);
|
||||
|
239
tilemap/examples/bmp.h
Normal file
239
tilemap/examples/bmp.h
Normal file
@ -0,0 +1,239 @@
|
||||
#ifndef BMP_H
|
||||
#define BMP_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
/**
|
||||
* @brief Simple BMP image writer - single header library
|
||||
*
|
||||
* Usage:
|
||||
* BmpWriter bmp(width, height);
|
||||
* bmp.set_pixel(x, y, r, g, b);
|
||||
* bmp.save("output.bmp");
|
||||
*/
|
||||
class BmpWriter {
|
||||
private:
|
||||
int width_;
|
||||
int height_;
|
||||
std::vector<std::uint8_t> pixels_;
|
||||
|
||||
struct BmpHeader {
|
||||
// BMP file header (14 bytes)
|
||||
std::uint8_t signature[2] = {'B', 'M'};
|
||||
std::uint32_t file_size;
|
||||
std::uint32_t reserved = 0;
|
||||
std::uint32_t data_offset = 54; // Header size
|
||||
|
||||
// DIB header (40 bytes)
|
||||
std::uint32_t dib_header_size = 40;
|
||||
std::int32_t width;
|
||||
std::int32_t height;
|
||||
std::uint16_t planes = 1;
|
||||
std::uint16_t bits_per_pixel = 24;
|
||||
std::uint32_t compression = 0;
|
||||
std::uint32_t image_size = 0;
|
||||
std::int32_t x_pixels_per_meter = 2835; // 72 DPI
|
||||
std::int32_t y_pixels_per_meter = 2835; // 72 DPI
|
||||
std::uint32_t colors_used = 0;
|
||||
std::uint32_t colors_important = 0;
|
||||
};
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Create a BMP writer with specified dimensions
|
||||
* @param width Image width in pixels
|
||||
* @param height Image height in pixels
|
||||
*/
|
||||
BmpWriter(int width, int height): width_(width), height_(height) {
|
||||
pixels_.resize(width * height * 3, 0); // RGB format
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set a pixel color
|
||||
* @param x X coordinate (0 to width-1)
|
||||
* @param y Y coordinate (0 to height-1)
|
||||
* @param r Red component (0-255)
|
||||
* @param g Green component (0-255)
|
||||
* @param b Blue component (0-255)
|
||||
*/
|
||||
void set_pixel(
|
||||
int x, int y, std::uint8_t r, std::uint8_t g, std::uint8_t b
|
||||
) {
|
||||
if (x >= 0 && x < width_ && y >= 0 && y < height_) {
|
||||
// BMP stores pixels bottom-to-top
|
||||
int flipped_y = height_ - 1 - y;
|
||||
int index = (flipped_y * width_ + x) * 3;
|
||||
pixels_[index] = b; // Blue
|
||||
pixels_[index + 1] = g; // Green
|
||||
pixels_[index + 2] = r; // Red
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set a pixel using a grayscale value
|
||||
* @param x X coordinate
|
||||
* @param y Y coordinate
|
||||
* @param gray Grayscale value (0-255)
|
||||
*/
|
||||
void set_pixel_gray(int x, int y, std::uint8_t gray) {
|
||||
set_pixel(x, y, gray, gray, gray);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set a pixel using a normalized value [0,1]
|
||||
* @param x X coordinate
|
||||
* @param y Y coordinate
|
||||
* @param value Normalized value (0.0 = black, 1.0 = white)
|
||||
*/
|
||||
void set_pixel_normalized(int x, int y, double value) {
|
||||
value = std::max(0.0, std::min(1.0, value)); // Clamp to [0,1]
|
||||
std::uint8_t gray = static_cast<std::uint8_t>(value * 255);
|
||||
set_pixel_gray(x, y, gray);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Fill the entire image with a color
|
||||
* @param r Red component (0-255)
|
||||
* @param g Green component (0-255)
|
||||
* @param b Blue component (0-255)
|
||||
*/
|
||||
void fill(std::uint8_t r, std::uint8_t g, std::uint8_t b) {
|
||||
for (int y = 0; y < height_; ++y) {
|
||||
for (int x = 0; x < width_; ++x) {
|
||||
set_pixel(x, y, r, g, b);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Draw a rectangle
|
||||
* @param x1 Left coordinate
|
||||
* @param y1 Top coordinate
|
||||
* @param x2 Right coordinate
|
||||
* @param y2 Bottom coordinate
|
||||
* @param r Red component (0-255)
|
||||
* @param g Green component (0-255)
|
||||
* @param b Blue component (0-255)
|
||||
*/
|
||||
void draw_rect(
|
||||
int x1, int y1, int x2, int y2, std::uint8_t r, std::uint8_t g,
|
||||
std::uint8_t b
|
||||
) {
|
||||
for (int y = y1; y <= y2; ++y) {
|
||||
for (int x = x1; x <= x2; ++x) {
|
||||
set_pixel(x, y, r, g, b);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Save the image to a BMP file
|
||||
* @param filename Output filename
|
||||
* @return true if successful, false otherwise
|
||||
*/
|
||||
bool save(const std::string &filename) {
|
||||
std::ofstream file(filename, std::ios::binary);
|
||||
if (!file.is_open()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Calculate row padding (BMP rows must be multiple of 4 bytes)
|
||||
int row_size = width_ * 3;
|
||||
int padding = (4 - (row_size % 4)) % 4;
|
||||
int padded_row_size = row_size + padding;
|
||||
|
||||
// Prepare header
|
||||
BmpHeader header;
|
||||
header.width = width_;
|
||||
header.height = height_;
|
||||
header.image_size = padded_row_size * height_;
|
||||
header.file_size = 54 + header.image_size;
|
||||
|
||||
// Write BMP file header
|
||||
file.write(reinterpret_cast<const char *>(header.signature), 2);
|
||||
file.write(reinterpret_cast<const char *>(&header.file_size), 4);
|
||||
file.write(reinterpret_cast<const char *>(&header.reserved), 4);
|
||||
file.write(reinterpret_cast<const char *>(&header.data_offset), 4);
|
||||
|
||||
// Write DIB header
|
||||
file.write(reinterpret_cast<const char *>(&header.dib_header_size), 4);
|
||||
file.write(reinterpret_cast<const char *>(&header.width), 4);
|
||||
file.write(reinterpret_cast<const char *>(&header.height), 4);
|
||||
file.write(reinterpret_cast<const char *>(&header.planes), 2);
|
||||
file.write(reinterpret_cast<const char *>(&header.bits_per_pixel), 2);
|
||||
file.write(reinterpret_cast<const char *>(&header.compression), 4);
|
||||
file.write(reinterpret_cast<const char *>(&header.image_size), 4);
|
||||
file.write(
|
||||
reinterpret_cast<const char *>(&header.x_pixels_per_meter), 4
|
||||
);
|
||||
file.write(
|
||||
reinterpret_cast<const char *>(&header.y_pixels_per_meter), 4
|
||||
);
|
||||
file.write(reinterpret_cast<const char *>(&header.colors_used), 4);
|
||||
file.write(reinterpret_cast<const char *>(&header.colors_important), 4);
|
||||
|
||||
// Write pixel data with padding
|
||||
std::vector<std::uint8_t> padding_bytes(padding, 0);
|
||||
for (int y = 0; y < height_; ++y) {
|
||||
// Write row data
|
||||
file.write(
|
||||
reinterpret_cast<const char *>(&pixels_[y * width_ * 3]),
|
||||
row_size
|
||||
);
|
||||
// Write padding
|
||||
if (padding > 0) {
|
||||
file.write(
|
||||
reinterpret_cast<const char *>(padding_bytes.data()),
|
||||
padding
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
file.close();
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get image width
|
||||
*/
|
||||
int width() const {
|
||||
return width_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get image height
|
||||
*/
|
||||
int height() const {
|
||||
return height_;
|
||||
}
|
||||
};
|
||||
|
||||
// Predefined colors for convenience
|
||||
namespace BmpColors {
|
||||
struct Color {
|
||||
std::uint8_t r, g, b;
|
||||
constexpr Color(std::uint8_t red, std::uint8_t green, std::uint8_t blue)
|
||||
: r(red), g(green), b(blue) {}
|
||||
};
|
||||
|
||||
constexpr Color BLACK(0, 0, 0);
|
||||
constexpr Color WHITE(255, 255, 255);
|
||||
constexpr Color RED(255, 0, 0);
|
||||
constexpr Color GREEN(0, 255, 0);
|
||||
constexpr Color BLUE(0, 0, 255);
|
||||
constexpr Color YELLOW(255, 255, 0);
|
||||
constexpr Color CYAN(0, 255, 255);
|
||||
constexpr Color MAGENTA(255, 0, 255);
|
||||
|
||||
// Tile type colors for terrain visualization
|
||||
constexpr Color LAND(144, 238, 144); // Light green
|
||||
constexpr Color MOUNTAIN(139, 69, 19); // Saddle brown
|
||||
constexpr Color SAND(244, 164, 96); // Sandy brown
|
||||
constexpr Color WATER(30, 144, 255); // Dodger blue
|
||||
constexpr Color ICE(176, 224, 230); // Powder blue
|
||||
} // namespace BmpColors
|
||||
|
||||
#endif // BMP_H
|
@ -1,38 +1,16 @@
|
||||
#include "bmp.h"
|
||||
#include "noise.h"
|
||||
#include <cstdlib>
|
||||
#include <fstream>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
// Convert a noise value [0,1] to a grayscale color
|
||||
std::string noise_to_grayscale(double noise_value) {
|
||||
// Clamp to [0,1] range
|
||||
noise_value = std::max(0.0, std::min(1.0, noise_value));
|
||||
|
||||
// Convert to 0-255 range
|
||||
int gray = static_cast<int>(noise_value * 255);
|
||||
|
||||
// Convert to hex color
|
||||
std::ostringstream oss;
|
||||
oss << "#" << std::hex << std::setfill('0') << std::setw(2) << gray
|
||||
<< std::setw(2) << gray << std::setw(2) << gray;
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
// Generate comparison SVG showing both raw and uniform noise side by side
|
||||
void generate_comparison_svg(
|
||||
// Generate comparison BMP showing both raw and uniform noise side by side
|
||||
void generate_comparison_bmp(
|
||||
const std::string &filename, int size, double scale, std::uint64_t seed,
|
||||
int octaves = 3, double persistence = 0.5
|
||||
) {
|
||||
std::ofstream file(filename);
|
||||
if (!file.is_open()) {
|
||||
std::cerr << "Error: Could not open output file: " << filename
|
||||
<< std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
// Create noise generators
|
||||
istd::PerlinNoise raw_noise(seed);
|
||||
istd::UniformPerlinNoise uniform_noise(seed);
|
||||
@ -40,17 +18,18 @@ void generate_comparison_svg(
|
||||
// Calibrate uniform noise
|
||||
uniform_noise.calibrate(scale, octaves, persistence);
|
||||
|
||||
const int pixel_size = 2;
|
||||
const int panel_width = size * pixel_size;
|
||||
const int svg_width = panel_width * 2 + 20; // Space for two panels + gap
|
||||
const int svg_height = size * pixel_size;
|
||||
const int panel_width = size;
|
||||
const int gap = 10;
|
||||
const int total_width = panel_width * 2 + gap;
|
||||
|
||||
// SVG header
|
||||
file << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
|
||||
file << "<svg width=\"" << svg_width << "\" height=\"" << svg_height
|
||||
<< "\" xmlns=\"http://www.w3.org/2000/svg\">\n";
|
||||
file << "<title>Noise Comparison: Raw vs Uniform (Scale: " << scale
|
||||
<< ", Octaves: " << octaves << ", Seed: " << seed << ")</title>\n";
|
||||
BmpWriter bmp(total_width, size);
|
||||
|
||||
// Fill gap with white
|
||||
for (int y = 0; y < size; ++y) {
|
||||
for (int x = panel_width; x < panel_width + gap; ++x) {
|
||||
bmp.set_pixel(x, y, 255, 255, 255);
|
||||
}
|
||||
}
|
||||
|
||||
// Generate histograms for statistics
|
||||
std::vector<int> raw_histogram(10, 0);
|
||||
@ -67,19 +46,12 @@ void generate_comparison_svg(
|
||||
int bin = std::min(9, static_cast<int>(noise_value * 10));
|
||||
raw_histogram[bin]++;
|
||||
|
||||
std::string color = noise_to_grayscale(noise_value);
|
||||
|
||||
int svg_x = x * pixel_size;
|
||||
int svg_y = y * pixel_size;
|
||||
|
||||
file << "<rect x=\"" << svg_x << "\" y=\"" << svg_y << "\" width=\""
|
||||
<< pixel_size << "\" height=\"" << pixel_size << "\" fill=\""
|
||||
<< color << "\"/>\n";
|
||||
bmp.set_pixel_normalized(x, y, noise_value);
|
||||
}
|
||||
}
|
||||
|
||||
// Generate right panel (uniform noise)
|
||||
int panel_offset = panel_width + 20;
|
||||
int panel_offset = panel_width + gap;
|
||||
for (int y = 0; y < size; ++y) {
|
||||
for (int x = 0; x < size; ++x) {
|
||||
double noise_value = uniform_noise.uniform_noise(x, y);
|
||||
@ -88,51 +60,17 @@ void generate_comparison_svg(
|
||||
int bin = std::min(9, static_cast<int>(noise_value * 10));
|
||||
uniform_histogram[bin]++;
|
||||
|
||||
std::string color = noise_to_grayscale(noise_value);
|
||||
|
||||
int svg_x = panel_offset + x * pixel_size;
|
||||
int svg_y = y * pixel_size;
|
||||
|
||||
file << "<rect x=\"" << svg_x << "\" y=\"" << svg_y << "\" width=\""
|
||||
<< pixel_size << "\" height=\"" << pixel_size << "\" fill=\""
|
||||
<< color << "\"/>\n";
|
||||
bmp.set_pixel_normalized(panel_offset + x, y, noise_value);
|
||||
}
|
||||
}
|
||||
|
||||
// Add labels
|
||||
file << "<text x=\"" << (panel_width / 2)
|
||||
<< "\" y=\"20\" font-family=\"Arial\" font-size=\"16\" "
|
||||
"font-weight=\"bold\" text-anchor=\"middle\" fill=\"white\" "
|
||||
"stroke=\"black\" stroke-width=\"1\">Raw Perlin Noise</text>\n";
|
||||
file << "<text x=\"" << (panel_offset + panel_width / 2)
|
||||
<< "\" y=\"20\" font-family=\"Arial\" font-size=\"16\" "
|
||||
"font-weight=\"bold\" text-anchor=\"middle\" fill=\"white\" "
|
||||
"stroke=\"black\" stroke-width=\"1\">Uniform Distribution</text>\n";
|
||||
if (!bmp.save(filename)) {
|
||||
std::cerr << "Error: Could not save BMP file: " << filename
|
||||
<< std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
// Add parameter info
|
||||
file << "<g transform=\"translate(10, " << (svg_height - 120) << ")\">\n";
|
||||
file << "<rect x=\"0\" y=\"0\" width=\"200\" height=\"100\" fill=\"white\" "
|
||||
"stroke=\"black\" stroke-width=\"1\" opacity=\"0.9\"/>\n";
|
||||
file << "<text x=\"10\" y=\"20\" font-family=\"Arial\" font-size=\"12\" "
|
||||
"font-weight=\"bold\">Parameters</text>\n";
|
||||
file << "<text x=\"10\" y=\"35\" font-family=\"Arial\" "
|
||||
"font-size=\"10\">Size: "
|
||||
<< size << "x" << size << "</text>\n";
|
||||
file << "<text x=\"10\" y=\"50\" font-family=\"Arial\" "
|
||||
"font-size=\"10\">Scale: "
|
||||
<< scale << "</text>\n";
|
||||
file << "<text x=\"10\" y=\"65\" font-family=\"Arial\" "
|
||||
"font-size=\"10\">Octaves: "
|
||||
<< octaves << "</text>\n";
|
||||
file << "<text x=\"10\" y=\"80\" font-family=\"Arial\" "
|
||||
"font-size=\"10\">Seed: "
|
||||
<< seed << "</text>\n";
|
||||
file << "</g>\n";
|
||||
|
||||
file << "</svg>\n";
|
||||
file.close();
|
||||
|
||||
std::cout << "Noise comparison SVG generated: " << filename << std::endl;
|
||||
std::cout << "Noise comparison BMP generated: " << filename << std::endl;
|
||||
std::cout << "Size: " << size << "x" << size << " pixels per panel"
|
||||
<< std::endl;
|
||||
std::cout << "Parameters: scale=" << scale << ", octaves=" << octaves
|
||||
@ -154,7 +92,7 @@ void generate_comparison_svg(
|
||||
int main(int argc, char *argv[]) {
|
||||
// Default parameters
|
||||
std::uint64_t seed = 12345;
|
||||
std::string output_filename = "noise_comparison.svg";
|
||||
std::string output_filename = "noise_comparison.bmp";
|
||||
double scale = 0.08;
|
||||
int octaves = 3;
|
||||
double persistence = 0.5;
|
||||
@ -178,8 +116,8 @@ int main(int argc, char *argv[]) {
|
||||
|
||||
if (argc == 1 || argc > 6) {
|
||||
std::cout << "Usage: " << argv[0]
|
||||
<< " [seed] [output.svg] [scale] [octaves] [persistence]\n";
|
||||
std::cout << "Defaults: seed=12345, output=noise_comparison.svg, "
|
||||
<< " [seed] [output.bmp] [scale] [octaves] [persistence]\n";
|
||||
std::cout << "Defaults: seed=12345, output=noise_comparison.bmp, "
|
||||
"scale=0.08, octaves=3, persistence=0.5\n";
|
||||
std::cout << "This will generate a side-by-side comparison of raw vs "
|
||||
"uniform Perlin noise\n";
|
||||
@ -195,7 +133,7 @@ int main(int argc, char *argv[]) {
|
||||
<< std::endl;
|
||||
|
||||
// Generate the comparison
|
||||
generate_comparison_svg(
|
||||
generate_comparison_bmp(
|
||||
output_filename, 256, scale, seed, octaves, persistence
|
||||
);
|
||||
|
||||
|
@ -1,51 +1,21 @@
|
||||
#include "bmp.h"
|
||||
#include "noise.h"
|
||||
#include <cstdlib>
|
||||
#include <fstream>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
// Convert a noise value [0,1] to a grayscale color
|
||||
std::string noise_to_grayscale(double noise_value) {
|
||||
// Clamp to [0,1] range
|
||||
noise_value = std::max(0.0, std::min(1.0, noise_value));
|
||||
|
||||
// Convert to 0-255 range
|
||||
int gray = static_cast<int>(noise_value * 255);
|
||||
|
||||
// Convert to hex color
|
||||
std::ostringstream oss;
|
||||
oss << "#" << std::hex << std::setfill('0') << std::setw(2) << gray
|
||||
<< std::setw(2) << gray << std::setw(2) << gray;
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
// Generate SVG visualization of Perlin noise
|
||||
void generate_noise_svg(
|
||||
// Generate BMP file from Perlin noise
|
||||
void generate_noise_bmp(
|
||||
const std::string &filename, int size, double scale, std::uint64_t seed,
|
||||
int octaves = 1, double persistence = 0.5
|
||||
) {
|
||||
std::ofstream file(filename);
|
||||
if (!file.is_open()) {
|
||||
std::cerr << "Error: Could not open output file: " << filename
|
||||
<< std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
// Create noise generator
|
||||
istd::PerlinNoise noise(seed);
|
||||
|
||||
const int pixel_size = 2; // Size of each pixel in SVG units
|
||||
const int svg_size = size * pixel_size;
|
||||
BmpWriter bmp(size, size);
|
||||
|
||||
// SVG header
|
||||
file << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
|
||||
file << "<svg width=\"" << svg_size << "\" height=\"" << svg_size
|
||||
<< "\" xmlns=\"http://www.w3.org/2000/svg\">\n";
|
||||
file << "<title>Perlin Noise Visualization (Scale: " << scale
|
||||
<< ", Octaves: " << octaves << ", Seed: " << seed << ")</title>\n";
|
||||
|
||||
// Generate noise values and create rectangles
|
||||
// Generate noise values and statistics
|
||||
double min_value = 1.0, max_value = 0.0;
|
||||
|
||||
for (int y = 0; y < size; ++y) {
|
||||
@ -63,66 +33,17 @@ void generate_noise_svg(
|
||||
min_value = std::min(min_value, noise_value);
|
||||
max_value = std::max(max_value, noise_value);
|
||||
|
||||
std::string color = noise_to_grayscale(noise_value);
|
||||
|
||||
int svg_x = x * pixel_size;
|
||||
int svg_y = y * pixel_size;
|
||||
|
||||
file << "<rect x=\"" << svg_x << "\" y=\"" << svg_y << "\" width=\""
|
||||
<< pixel_size << "\" height=\"" << pixel_size << "\" fill=\""
|
||||
<< color << "\"/>\n";
|
||||
bmp.set_pixel_normalized(x, y, noise_value);
|
||||
}
|
||||
}
|
||||
|
||||
// Add information text
|
||||
file << "<!-- Statistics: Min=" << min_value << " Max=" << max_value
|
||||
<< " -->\n";
|
||||
|
||||
// Add parameter info text overlay
|
||||
file << "<g transform=\"translate(10, 10)\">\n";
|
||||
file << "<rect x=\"0\" y=\"0\" width=\"300\" height=\"120\" fill=\"white\" "
|
||||
"stroke=\"black\" stroke-width=\"1\" opacity=\"0.9\"/>\n";
|
||||
file << "<text x=\"10\" y=\"20\" font-family=\"Arial\" font-size=\"14\" "
|
||||
"font-weight=\"bold\">Perlin Noise Parameters</text>\n";
|
||||
file << "<text x=\"10\" y=\"40\" font-family=\"Arial\" "
|
||||
"font-size=\"12\">Size: "
|
||||
<< size << "x" << size << "</text>\n";
|
||||
file << "<text x=\"10\" y=\"55\" font-family=\"Arial\" "
|
||||
"font-size=\"12\">Scale: "
|
||||
<< scale << "</text>\n";
|
||||
file << "<text x=\"10\" y=\"70\" font-family=\"Arial\" "
|
||||
"font-size=\"12\">Seed: "
|
||||
<< seed << "</text>\n";
|
||||
file << "<text x=\"10\" y=\"85\" font-family=\"Arial\" "
|
||||
"font-size=\"12\">Octaves: "
|
||||
<< octaves << "</text>\n";
|
||||
file << "<text x=\"10\" y=\"100\" font-family=\"Arial\" "
|
||||
"font-size=\"12\">Range: ["
|
||||
<< std::fixed << std::setprecision(3) << min_value << ", " << max_value
|
||||
<< "]</text>\n";
|
||||
file << "</g>\n";
|
||||
|
||||
// Add grayscale legend
|
||||
file << "<g transform=\"translate(" << (svg_size - 60) << ", 10)\">\n";
|
||||
file << "<text x=\"0\" y=\"15\" font-family=\"Arial\" font-size=\"12\" "
|
||||
"font-weight=\"bold\">Value</text>\n";
|
||||
for (int i = 0; i <= 10; ++i) {
|
||||
double value = i / 10.0;
|
||||
std::string color = noise_to_grayscale(value);
|
||||
int y_pos = 20 + i * 15;
|
||||
file << "<rect x=\"0\" y=\"" << y_pos
|
||||
<< "\" width=\"20\" height=\"12\" fill=\"" << color
|
||||
<< "\" stroke=\"black\" stroke-width=\"0.5\"/>\n";
|
||||
file << "<text x=\"25\" y=\"" << (y_pos + 9)
|
||||
<< "\" font-family=\"Arial\" font-size=\"10\">" << std::fixed
|
||||
<< std::setprecision(1) << value << "</text>\n";
|
||||
if (!bmp.save(filename)) {
|
||||
std::cerr << "Error: Could not save BMP file: " << filename
|
||||
<< std::endl;
|
||||
return;
|
||||
}
|
||||
file << "</g>\n";
|
||||
|
||||
file << "</svg>\n";
|
||||
file.close();
|
||||
|
||||
std::cout << "Perlin noise SVG generated: " << filename << std::endl;
|
||||
std::cout << "Perlin noise BMP generated: " << filename << std::endl;
|
||||
std::cout << "Size: " << size << "x" << size << " pixels" << std::endl;
|
||||
std::cout << "Scale: " << scale << ", Octaves: " << octaves << std::endl;
|
||||
std::cout << "Value range: [" << std::fixed << std::setprecision(3)
|
||||
@ -132,7 +53,7 @@ void generate_noise_svg(
|
||||
int main(int argc, char *argv[]) {
|
||||
// Default parameters
|
||||
std::uint64_t seed = 12345;
|
||||
std::string output_filename = "perlin_noise.svg";
|
||||
std::string output_filename = "perlin_noise.bmp";
|
||||
double scale = 0.02;
|
||||
int octaves = 1;
|
||||
double persistence = 0.5;
|
||||
@ -156,12 +77,12 @@ int main(int argc, char *argv[]) {
|
||||
|
||||
if (argc == 1 || argc > 6) {
|
||||
std::cout << "Usage: " << argv[0]
|
||||
<< " [seed] [output.svg] [scale] [octaves] [persistence]\n";
|
||||
std::cout << "Defaults: seed=12345, output=perlin_noise.svg, "
|
||||
<< " [seed] [output.bmp] [scale] [octaves] [persistence]\n";
|
||||
std::cout << "Defaults: seed=12345, output=perlin_noise.bmp, "
|
||||
"scale=0.02, octaves=1, persistence=0.5\n";
|
||||
std::cout << "Examples:\n";
|
||||
std::cout << " " << argv[0] << " 54321 noise1.svg 0.01\n";
|
||||
std::cout << " " << argv[0] << " 12345 octave_noise.svg 0.02 4 0.5\n";
|
||||
std::cout << " " << argv[0] << " 54321 noise1.bmp 0.01\n";
|
||||
std::cout << " " << argv[0] << " 12345 octave_noise.bmp 0.02 4 0.5\n";
|
||||
if (argc > 6) {
|
||||
return 1;
|
||||
}
|
||||
@ -188,7 +109,7 @@ int main(int argc, char *argv[]) {
|
||||
<< std::endl;
|
||||
|
||||
// Generate the noise visualization
|
||||
generate_noise_svg(output_filename, 256, scale, seed, octaves, persistence);
|
||||
generate_noise_bmp(output_filename, 256, scale, seed, octaves, persistence);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user