diff --git a/tilemap/examples/biome_demo.cpp b/tilemap/examples/biome_demo.cpp index 7306099..a35b0eb 100644 --- a/tilemap/examples/biome_demo.cpp +++ b/tilemap/examples/biome_demo.cpp @@ -1,50 +1,39 @@ +#include "bmp.h" #include "generation.h" #include "tile.h" #include "tilemap.h" #include -#include #include #include #include -// 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 << "\n"; - file << "\n"; - file << "Tilemap Visualization\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 << "\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 << "\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 << "\n"; - // Horizontal lines - file << "\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 << "\n"; - file << "\n"; - file << "\n"; - file << "Legend\n"; - - const std::pair 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 << "\n"; - file << "" - << legend_items[i].second << "\n"; - } - file << "\n"; - - file << "\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] << " \n"; - std::cerr << "Example: " << argv[0] << " 12345 output.svg\n"; + std::cerr << "Usage: " << argv[0] << " \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); diff --git a/tilemap/examples/bmp.h b/tilemap/examples/bmp.h new file mode 100644 index 0000000..45cc807 --- /dev/null +++ b/tilemap/examples/bmp.h @@ -0,0 +1,239 @@ +#ifndef BMP_H +#define BMP_H + +#include +#include +#include +#include + +/** + * @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 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(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(header.signature), 2); + file.write(reinterpret_cast(&header.file_size), 4); + file.write(reinterpret_cast(&header.reserved), 4); + file.write(reinterpret_cast(&header.data_offset), 4); + + // Write DIB header + file.write(reinterpret_cast(&header.dib_header_size), 4); + file.write(reinterpret_cast(&header.width), 4); + file.write(reinterpret_cast(&header.height), 4); + file.write(reinterpret_cast(&header.planes), 2); + file.write(reinterpret_cast(&header.bits_per_pixel), 2); + file.write(reinterpret_cast(&header.compression), 4); + file.write(reinterpret_cast(&header.image_size), 4); + file.write( + reinterpret_cast(&header.x_pixels_per_meter), 4 + ); + file.write( + reinterpret_cast(&header.y_pixels_per_meter), 4 + ); + file.write(reinterpret_cast(&header.colors_used), 4); + file.write(reinterpret_cast(&header.colors_important), 4); + + // Write pixel data with padding + std::vector padding_bytes(padding, 0); + for (int y = 0; y < height_; ++y) { + // Write row data + file.write( + reinterpret_cast(&pixels_[y * width_ * 3]), + row_size + ); + // Write padding + if (padding > 0) { + file.write( + reinterpret_cast(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 diff --git a/tilemap/examples/noise_comparison.cpp b/tilemap/examples/noise_comparison.cpp index 3ec65b4..f18501f 100644 --- a/tilemap/examples/noise_comparison.cpp +++ b/tilemap/examples/noise_comparison.cpp @@ -1,38 +1,16 @@ +#include "bmp.h" #include "noise.h" #include -#include #include #include -#include #include +#include -// 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(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 << "\n"; - file << "\n"; - file << "Noise Comparison: Raw vs Uniform (Scale: " << scale - << ", Octaves: " << octaves << ", Seed: " << seed << ")\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 raw_histogram(10, 0); @@ -67,19 +46,12 @@ void generate_comparison_svg( int bin = std::min(9, static_cast(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 << "\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(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 << "\n"; + bmp.set_pixel_normalized(panel_offset + x, y, noise_value); } } - // Add labels - file << "Raw Perlin Noise\n"; - file << "Uniform Distribution\n"; + if (!bmp.save(filename)) { + std::cerr << "Error: Could not save BMP file: " << filename + << std::endl; + return; + } - // Add parameter info - file << "\n"; - file << "\n"; - file << "Parameters\n"; - file << "Size: " - << size << "x" << size << "\n"; - file << "Scale: " - << scale << "\n"; - file << "Octaves: " - << octaves << "\n"; - file << "Seed: " - << seed << "\n"; - file << "\n"; - - file << "\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 ); diff --git a/tilemap/examples/perlin_demo.cpp b/tilemap/examples/perlin_demo.cpp index 109192a..de692e6 100644 --- a/tilemap/examples/perlin_demo.cpp +++ b/tilemap/examples/perlin_demo.cpp @@ -1,51 +1,21 @@ +#include "bmp.h" #include "noise.h" #include -#include #include #include #include -// 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(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 << "\n"; - file << "\n"; - file << "Perlin Noise Visualization (Scale: " << scale - << ", Octaves: " << octaves << ", Seed: " << seed << ")\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 << "\n"; + bmp.set_pixel_normalized(x, y, noise_value); } } - // Add information text - file << "\n"; - - // Add parameter info text overlay - file << "\n"; - file << "\n"; - file << "Perlin Noise Parameters\n"; - file << "Size: " - << size << "x" << size << "\n"; - file << "Scale: " - << scale << "\n"; - file << "Seed: " - << seed << "\n"; - file << "Octaves: " - << octaves << "\n"; - file << "Range: [" - << std::fixed << std::setprecision(3) << min_value << ", " << max_value - << "]\n"; - file << "\n"; - - // Add grayscale legend - file << "\n"; - file << "Value\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 << "\n"; - file << "" << std::fixed - << std::setprecision(1) << value << "\n"; + if (!bmp.save(filename)) { + std::cerr << "Error: Could not save BMP file: " << filename + << std::endl; + return; } - file << "\n"; - file << "\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; }