Multithreaded Mandelbrot set code outputs incorrect image - c++

#include <chrono>
#include <cstdint>
#include <cstdlib>
#include <complex>
#include <fstream>
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
using std::chrono::duration_cast;
using std::chrono::milliseconds;
using std::chrono::seconds;
using std::complex;
using std::cout;
using std::endl;
using std::this_thread::sleep_for;
using std::ofstream;
using std::thread;
using std::mutex;
using std::condition_variable;
using std::unique_lock;
mutex mutex1;
mutex mutex2;
std::condition_variable done;
typedef std::chrono::steady_clock the_clock;
const int WIDTH1 = 960;
const int HEIGHT1 = 600;
const int WIDTH2 = 1920;
const int HEIGHT2 = 1200;
int finished_threads = 0;
const int MAX_ITERATIONS = 500;
uint32_t image[HEIGHT2][WIDTH2];
struct ThreadArgs { int id; int delay; };
void myThreadFunc(ThreadArgs args)
{
for (int i = 0; i < 1; i++) {
sleep_for(seconds(args.delay));
cout << args.id;
}
}
void write_tga(const char *filename)
{
unique_lock<mutex> lock(mutex2);
while (finished_threads < 2) {
done.wait(lock);
}
ofstream outfile(filename, ofstream::binary);
uint8_t header[18] = {
0, // no image ID
0, // no colour map
2, // uncompressed 24-bit image
0, 0, 0, 0, 0, // empty colour map specification
0, 0, // X origin
0, 0, // Y origin
WIDTH2 & 0xFF, (WIDTH2 >> 8) & 0xFF, // width
HEIGHT2 & 0xFF, (HEIGHT2 >> 8) & 0xFF, // height
24, // bits per pixel
0, // image descriptor
};
outfile.write((const char *)header, 18);
for (int y = 0; y < HEIGHT2; ++y)
{
for (int x = 0; x < WIDTH2; ++x)
{
uint8_t pixel[3] = {
image[y][x] & 0xFF, // blue channel
(image[y][x] >> 8) & 0xFF, // green channel
(image[y][x] >> 16) & 0xFF, // red channel
};
outfile.write((const char *)pixel, 3);
}
}
outfile.close();
if (!outfile)
{
cout << "Error writing to " << filename << endl;
exit(1);
}
}
// Render the Mandelbrot set into the image array.
// The parameters specify the region on the complex plane to plot.
void compute_mandelbrot(double left, double right, double top, double bottom)
{
unique_lock<mutex> lock(mutex2);
for (int y = 0; y < HEIGHT1; ++y)
{
for (int x = 0; x < WIDTH1; ++x)
{
complex<double> c(left + (x * (right - left) / WIDTH2),
top + (y * (bottom - top) / HEIGHT2));
// Start off z at (0, 0).
complex<double> z(0.0, 0.0);
// Iterate z = z^2 + c until z moves more than 2 units
// away from (0, 0), or we've iterated too many times.
int iterations = 0;
mutex1.lock();
while (abs(z) < 2.0 && iterations < MAX_ITERATIONS)
{
z = (z * z) + c;
++iterations;
}
mutex1.unlock();
if (iterations == MAX_ITERATIONS)
{
image[y][x] = 0x000000; // black
}
else
{
image[y][x] = 0xFFFFFF; // white
finished_threads = finished_threads + 1;
done.notify_all();
}
}
}
}
void compute_mandelbrot2(double left2, double right2, double top2, double bottom2)
{
unique_lock<mutex> lock(mutex2);
//map <int, int> val = map<int, int>(0, MAX_ITERATIONS);
//map <int, int> colourval = map<int, int>(0, MAX_ITERATIONS);
for (int y = HEIGHT1; y < HEIGHT2; ++y)
{
for (int x = HEIGHT1; x < WIDTH2; ++x)
{
complex<double> c(left2 + (x * (right2 - left2) / WIDTH2),
top2 + (y * (bottom2 - top2) / HEIGHT2));
// Start off z at (0, 0).
complex<double> z(0.0, 0.0);
// Iterate z = z^2 + c until z moves more than 2 units
int iterations = 0;
mutex1.lock();
while (abs(z) < 2.0 && iterations < MAX_ITERATIONS)
{
z = (z * z) + c;
++iterations;
}
mutex1.unlock();
if (iterations == MAX_ITERATIONS)
{
// z didn't escape from the circle.
// This point is in the Mandelbrot set.
image[y][x] = 0x000000; // black
}
else
{
// z escaped within less than MAX_ITERATIONS
// iterations. This point isn't in the set.
image[y][x] = 0xFFFFFF; // white
finished_threads = finished_threads + 1;
done.notify_one();
}
}
}
}
int main(int argc, char *argv[])
{
std::thread myThread;
std::thread myThread2;
std::thread myThread3;
ThreadArgs args;
myThread = std::thread(compute_mandelbrot, -2.0, 1.0, 1.125, -1.125);
myThread3 = std::thread(compute_mandelbrot2, -2.0, 1.0, 1.125, -1.125);
myThread2 = std::thread(write_tga, "output.tga");
cout << "Please wait..." << endl;
// Start timing
the_clock::time_point start = the_clock::now();
myThread.join();
myThread3.join();
// Stop timing
the_clock::time_point end = the_clock::now();
// Compute the difference between the two times in milliseconds
auto time_taken = duration_cast<milliseconds>(end - start).count();
cout << "Computing the Mandelbrot set took " << time_taken << " ms." << endl;
myThread2.join();
return 0;
}
Above is the multithreaded version of the code and it outputs an incorrect version of the set where most of it is black but some is correct, so I don't know what the issue is:Mandlebrot threaded but the non-threaded version outputs a correct Mandelbrot setMandelbrot not threaded the problem is likely something to do with how I used multithreading: I just don't know what I did wrong. Any help is appreciated.

OK, 'simple' things first! You have a 'typo' in your compute_mandelbrot2 function on the control statement for your inner (x) loop; This line:
for (int x = HEIGHT1; x < WIDTH2; ++x)
should (of course) be:
for (int x = WIDTH1; x < WIDTH2; ++x) // WIDTH1 not HEIGHT1
Now for the more 'subtle' stuff. You are trying to split the calculation into two halves, by splitting both the 'x' and 'y' ranges into halves. This will not work, as that would require four threads, each dealing with the relevant quarter of the plot. To keep to using just two halves, the 'y' ranges in the two thread functions must cover the whole plot (but the 'x' ranges can properly be split in two).
Thus, your outer ('y') loop control statements should cover the entire range in both thread functions, and they should both be this:
for (int y = 0; y < HEIGHT2; ++y) {
//...
I have tested your code with the aforementioned three changes and it produces the correct Mandelbrot set image. Feel free to ask for for further clarification and/or explanation.

Related

trying to use threads to make the mandelbrot set faster by splitting the function that computes the image into 2 thread but i can't get it to work

const int WIDTH = 1920;
const int HEIGHT = 1200;
const int MAX_ITERATIONS = 500;
uint32_t image[HEIGHT][WIDTH];
struct ThreadArgs { int id; int delay; };
void myThreadFunc(ThreadArgs args)
{
for (int i = 0; i < 1; i++) {
sleep_for(seconds(args.delay));
cout << args.id;
}
}
void write_tga(const char *filename)
{
ofstream outfile(filename, ofstream::binary);
uint8_t header[18] = {
0, // no image ID
0, // no colour map
2, // uncompressed 24-bit image
0, 0, 0, 0, 0, // empty colour map specification
0, 0, // X origin
0, 0, // Y origin
WIDTH & 0xFF, (WIDTH >> 8) & 0xFF, // width
HEIGHT & 0xFF, (HEIGHT >> 8) & 0xFF, // height
24, // bits per pixel
0, // image descriptor
};
outfile.write((const char *)header, 18);
for (int y = 0; y < HEIGHT; ++y)
{
for (int x = 0; x < WIDTH; ++x)
{
uint8_t pixel[3] = {
image[y][x] & 0xFF, // blue channel
(image[y][x] >> 8) & 0xFF, // green channel
(image[y][x] >> 16) & 0xFF, // red channel
};
outfile.write((const char *)pixel, 3);
}
}
outfile.close();
if (!outfile)
{
cout << "Error writing to " << filename << endl;
exit(1);
}
}
void compute_mandelbrot(double left, double right, double top, double bottom)
{
for (int y = 0; y < HEIGHT; ++y)
{
for (int x = 0; x < WIDTH; ++x)
{
complex<double> c(left + (x * (right - left) / WIDTH),
top + (y * (bottom - top) / HEIGHT));
complex<double> z(0.0, 0.0);
int iterations = 0;
while (abs(z) < 2.0 && iterations < MAX_ITERATIONS)
{
z = (z * z) + c;
++iterations;
}
if (iterations == MAX_ITERATIONS)
{
image[y][x] = 0x000000; // black
}
else
{
image[y][x] = 0xFFFFFF; // white
}
}
}
}
this is most of the code and this works but i want to make it run faster using more threads.
i tried splitting the height portions of the compute_mandelbrot function into two separate functions but could not get it to not flag errors. the errors i got were: "expression must be a modifiable lvalue" and "array type 'uint32_t[1920]' is not assignable" on the line "image[x] = 0x000000 ' the same happened on the other lines mentioning image[x] or image[y] as I'd changed those lines to split the function between x axis and y axis. The above code does not have this change
is there any way to do this or something like this to split this function between two threads? if so please explain
Compilers don't guess at your code. uint32_t image[HEIGHT][WIDTH]; is a 2D array of pixels. image[y][x] is single pixel. image[y] is a row of pixels. And you can't assign a whole row of pixels. image[x] is still a row of pixels. The compile won't turn that into a column just because you used x.
It's indeed easy to split the function in two threads. Just calculate the upper and lower half of the image separately, changing just the for(int y ... part.

How to fix vertical artifact lines in a vertex array in SFML, WITH pixel perfect zoom/move?

I have been working on a 2D top-down game solely in SFML and C++, that has a tilemap. I cannot rid the tilemap of vertical line artifacts when zooming in and out or moving the render view at different zoom levels. I attached an image below of the problem; circled in red.
[edit]
There are a lot of factors that make this bug inconsistent.
If I use a tile_atlas from only one tile, there is no artifacts. If I map each texture to a tile a.k.a not using vertex arrays; I did not see any artifacts but it is anywhere from 10x to 15x slower with the same number of tiles on the screen. I have tried finding zoom levels that don't cause artifacts, and there are some. but the levels are almost random and makes zoom in and out, not smooth and choppy.
I have tried numerous tutorials and forum "fixes" that have not completely worked. I have completely rewrote the underlying tile engine 4 separate times to no avail.
https://en.sfml-dev.org/forums/index.php?topic=15747.0
topic=14504
topic=5952
topic=13637.15
https://www.sfml-dev.org/tutorials/2.5/graphics-view.php
https://www.binpress.com/creating-city-building-game-with-sfml/
https://www.sfml-dev.org/tutorials/2.5/graphics-vertex-array.php
[edit]
I have read the Terreria scaling issue, the fix to make extra large textures then scale, or multiple textures, one for each zoom level. seem exhaustive. I am looking for a programmatic way of achieving the scaling correctly.
This is post is my last attempt to fix the code, otherwise I will need to change languages.
I believe the main issue comes from the zoom in/out functions of the program.
I have tried many, variations/attempt to get this to work +0.5f offset,+0.375f offset, not pixel perfect
if (sf::Keyboard::isKeyPressed(sf::Keyboard::E))
{
float zoom_in = 0.995f;
float nz = last_sq * zoom_in;
nz = std::floor(nz);
float now = nz / last_sq;
if (nz <= 10)
continue;
last_sq = nz;
std::cout << now << std::endl;
cam.zoom(now);
}
if (sf::Keyboard::isKeyPressed(sf::Keyboard::W))
{
cam.move(0.f, -0.02f);
float x = cam.getCenter().x;
float y = cam.getCenter().y;
x = std::floor(x);
y = std::floor(y);
//std::cout << "x: " << x << "\ty: " << y << std::endl;
cam.setCenter(x, y);
}
Here is the entire code.
main.cpp
#include "chunk_map.h"
#include "tile_atlas.h"
#include <vector>
//#include "main.h"
#include "map.h"
#include <iostream>
#include <SFML/Graphics/RenderWindow.hpp>
#include <SFML/Graphics/View.hpp>
#include <SFML/Window/Event.hpp>
#include <SFML/Window/Keyboard.hpp>
#include "animation_handler.h"
int main(int argc, char* argv[])
{
sf::RenderWindow app(sf::VideoMode(600, 600), "Tilemap Example");
// Hard set fps to monitor refresh rate.
// textures to load.
/*text_mgr.loadTexture("grass", "grass.png");
text_mgr.loadTexture("high_grass", "high_grass.png");
Animation staticAnim(0, 0, 1.0f);
tileAtlas["grass"] = Tile(32, 1, text_mgr.getRef("grass"),{ staticAnim },
TileType::GRASS, 50, 0, 1);
tileAtlas["high_grass"] = Tile(32, 1, text_mgr.getRef("high_grass"),{ staticAnim },
TileType::HIGH_GRASS, 100, 0, 1);*/
//Map map;
//map.load(50, 50, tileAtlas);
#ifdef NDEBUG
app.setVerticalSyncEnabled(true);
std::cout << "#################\n";
#endif // DEBUG
//app.setVerticalSyncEnabled(true);
sf::View cam = app.getDefaultView();
tile_atlas atlas = tile_atlas();
std::vector<chunk_map> map;
for (int x = 0; x < 5; x++)
{
for (int y = 0; y < 5; y++)
{
map.push_back(chunk_map());
map.back().set_texture(atlas.get_atlas());
map.back().set_position(10 * x, 10 * y, 10 * (x + 1), 10 * (y + 1));
map.back().load_tiles();
}
}
sf::Clock clock;
int checked = 0;
int last_sq = 600;
while (app.isOpen())
{
//sf::Time elapsed = clock.restart();
//float dt = elapsed.asSeconds();
sf::Event eve;
while (app.pollEvent(eve))
if (eve.type == sf::Event::Closed)
app.close();
if (sf::Keyboard::isKeyPressed(sf::Keyboard::P))
std::cout << "view x: " << cam.getSize().x << "\tview y: " << cam.getSize().y << std::endl;
if (sf::Keyboard::isKeyPressed(sf::Keyboard::Q))
{
float zoom_out = 1.005f;
float nz = last_sq * zoom_out;
nz = std::ceil(nz);
float now = nz / last_sq;
last_sq = nz;
std::cout << now << std::endl;
cam.zoom(now);
//float x = cam.getCenter().x;
//float y = cam.getCenter().y;
//x = std::floor(x);
//y = std::floor(y);
////std::cout << "x: " << x << "\ty: " << y << std::endl;
//cam.setCenter(x, y);
}
if (sf::Keyboard::isKeyPressed(sf::Keyboard::E))
{
float zoom_in = 0.995f;
float nz = last_sq * zoom_in;
nz = std::floor(nz);
float now = nz / last_sq;
if (nz <= 10)
continue;
last_sq = nz;
std::cout << now << std::endl;
cam.zoom(now);
//float x = cam.getCenter().x;
//float y = cam.getCenter().y;
//x = std::floor(x);
//y = std::floor(y);
////std::cout << "x: " << x << "\ty: " << y << std::endl;
//cam.setCenter(x, y);
}
if (sf::Keyboard::isKeyPressed(sf::Keyboard::W))
{
cam.move(0.f, -0.02f);
float x = cam.getCenter().x;
float y = cam.getCenter().y;
x = std::floor(x);
y = std::floor(y);
//std::cout << "x: " << x << "\ty: " << y << std::endl;
cam.setCenter(x, y);
}
if (sf::Keyboard::isKeyPressed(sf::Keyboard::A))
{
cam.move(-0.02f, 0.f);
float x = cam.getCenter().x;
float y = cam.getCenter().y;
x = std::floor(x);
y = std::floor(y);
//std::cout << "x: " << x << "\ty: " << y << std::endl;
cam.setCenter(x, y);
}
if (sf::Keyboard::isKeyPressed(sf::Keyboard::S))
{
cam.move(0.f, 0.02f);
float x = cam.getCenter().x;
float y = cam.getCenter().y;
x = std::ceil(x);
y = std::ceil(y);
//std::cout << "x: " << x << "\ty: " << y << std::endl;
cam.setCenter(x, y);
}
if (sf::Keyboard::isKeyPressed(sf::Keyboard::D))
{
cam.move(0.02f, 0.f);
float x = cam.getCenter().x;
float y = cam.getCenter().y;
x = std::ceil(x);
y = std::ceil(y);
//std::cout << "x: " << x << "\ty: " << y << std::endl;
cam.setCenter(x, y);
}
if (sf::Keyboard::isKeyPressed(sf::Keyboard::Right))
{
sf::Time elapsed = clock.getElapsedTime();
float t = elapsed.asSeconds();
int time = std::floor(t);
if (checked < time)
{
checked = time;
cam.move(0.01f, 0.f);
float x = cam.getCenter().x;
float y = cam.getCenter().y;
x = std::ceil(x);
y = std::ceil(y);
std::cout << "x: " << x << "\ty: " << y << std::endl;
cam.setCenter(x, y);
}
}
if (sf::Keyboard::isKeyPressed(sf::Keyboard::Escape))
app.close();
app.setView(cam);
#ifdef _DEBUG
app.clear();
#endif // DEBUG
//map.draw(app, dt);
for (int i = 0; i < 25; i++)
{
app.draw(map.at(i));
}
app.display();
}
}
chunk_map.h
#pragma once
#include <SFML/Graphics/Drawable.hpp>
#include <SFML/Graphics/Texture.hpp>
#include <SFML/Graphics/VertexArray.hpp>
#include <vector>
class chunk_map : public sf::Drawable
{
private:
//change values of these to match your needs and improve performance
enum { tilesize = 32, chunksize = 32};
//tile size float
float tile_size_float = 32.0f;
// Draw chunk
virtual void draw(sf::RenderTarget& target, sf::RenderStates states)const;
// texture for chunk
sf::Texture m_texture;
// chunk dimensions
int tiles_per_chunk_x;
int tiles_per_chunk_y;
//start x and y and ending x and y scaled to tile size. a.k.a.
// 1,1 = tile 1,1. 10,10, equals tile 10,10
int chunk_start_x;
int chunk_start_y;
int chunk_end_x;
int chunk_end_y;
// Vertex array of positions of tiles in chunk
std::vector<std::vector<sf::VertexArray> > m_chunks;
// Append tiles.
void append_tile(int gx, int gy, sf::VertexArray& garr);
public:
chunk_map();
~chunk_map();
void load_tiles();
void set_texture(sf::Texture);
void set_position(int chunk_start_x, int chunk_start_y,
int chunk_end_x, int chunk_end_y);
};
chunk_map.cpp
#include "chunk_map.h"
#include <SFML/Graphics/RenderTarget.hpp>
#include <SFML/System/Vector2.hpp>
#include <SFML/Graphics/Vertex.hpp>
chunk_map::chunk_map()
{
}
chunk_map::~chunk_map()
{
}
void chunk_map::load_tiles()
{
/*
Tile loading this is were the tiles are added to the Quadrantics of the tilemap.
this is the entire chunk_map loop
*/
if ((chunk_end_x * chunk_end_y) == 0)//empty map - possibly forgotten to fill data struct
{
//to stop displaying at all after failed loading:
tiles_per_chunk_x = 0;
tiles_per_chunk_y = 0;
m_chunks.clear();
return;
}
chunk_map::tiles_per_chunk_x = (chunk_end_x / chunksize) + 1;
chunk_map::tiles_per_chunk_y = (chunk_end_y / chunksize) + 1;
m_chunks.assign(tiles_per_chunk_x, std::vector<sf::VertexArray>(tiles_per_chunk_y, sf::VertexArray(sf::Quads)));//ready up empty 2d arrays
for (int iy = chunk_start_y; iy < chunk_end_y; ++iy)
{
for (int ix = chunk_start_x; ix < chunk_end_x; ++ix)
{
append_tile(ix, iy, m_chunks[ix / chunksize][iy / chunksize]);
}
}
}
void chunk_map::append_tile(int gx, int gy, sf::VertexArray& garr)
{
/*
This is the specific tile vertex, broken from the other function to decrease complexitity.
*/
int tile_selection_index_x = rand() % 2;
int tile_selection_index_y = 0;
float f_tx = tile_selection_index_x * tile_size_float;
float f_ty = tile_selection_index_y * tile_size_float;
sf::Vertex ver;
//____________________________________________________________________________________________________________
ver.position = sf::Vector2f(gx * tile_size_float, gy * tile_size_float);
//texture in position of text atlas
//top left corner
//ver.texCoords = sf::Vector2f( 0.f, 0.f);
ver.texCoords = sf::Vector2f(f_tx, f_ty);
garr.append(ver);
//____________________________________________________________________________________________________________
ver.position = sf::Vector2f(gx * tile_size_float + tile_size_float, gy * tile_size_float);
//texture in position of text atlas
//top right corner
//ver.texCoords = sf::Vector2f( tile_size_float, 0.f);
ver.texCoords = sf::Vector2f(f_tx + tile_size_float, f_ty);
garr.append(ver);
//____________________________________________________________________________________________________________
ver.position = sf::Vector2f(gx * tile_size_float + tile_size_float, gy * tile_size_float + tile_size_float);
//texture in position of text atlas
//bottom right corner
//ver.texCoords = sf::Vector2f( tile_size_float, tile_size_float);
ver.texCoords = sf::Vector2f(f_tx + tile_size_float, f_ty + tile_size_float);
garr.append(ver);
//____________________________________________________________________________________________________________
ver.position = sf::Vector2f(gx * tile_size_float, gy * tile_size_float + tile_size_float);
//texture in position of text atlas
//bottom left corner
//ver.texCoords = sf::Vector2f( 0.f, tile_size_float);
ver.texCoords = sf::Vector2f(f_tx, f_ty + tile_size_float);
garr.append(ver);
}
void chunk_map::set_texture(sf::Texture t)
{
/*
Sets the texture data for this chunk map from the texture atlas.
*/
m_texture = t;
// TODO test this feature
// Attempt to optimize tearing on zooming to a different view.
//m_texture.setSmooth(true);
}
void chunk_map::set_position(int chunk_start_x, int chunk_start_y,
int chunk_end_x, int chunk_end_y)
{
/*
Initialize the accordinates of the start of the chunk_map to the end.
*/
chunk_map::chunk_start_x = chunk_start_x;
chunk_map::chunk_start_y = chunk_start_y;
chunk_map::chunk_end_x = chunk_end_x;
chunk_map::chunk_end_y = chunk_end_y;
}
void chunk_map::draw(sf::RenderTarget& target, sf::RenderStates states)const
{
/*
The actual draw call to this specific chunk_map
*/
// position variables for this draw.
int left = 0;
int right = 0;
int top = 0;
int bottom = 0;
//get top left point of view
sf::Vector2f temp = target.getView().getCenter() - (target.getView().getSize() / 2.f);
//get top left point of view
left = static_cast<int>(temp.x / (chunksize * tilesize));
top = static_cast<int>(temp.y / (chunksize * tilesize));
//get bottom right point of view
temp += target.getView().getSize();
right = 1 + static_cast<int>(temp.x / (chunksize * tilesize));
bottom = 1 + static_cast<int>(temp.y / (chunksize * tilesize));
//clamp these to fit into array bounds:
left = std::max(0, std::min(left, tiles_per_chunk_x));
top = std::max(0, std::min(top, tiles_per_chunk_y));
right = std::max(0, std::min(right, tiles_per_chunk_x));
bottom = std::max(0, std::min(bottom, tiles_per_chunk_y));
//set texture and draw visible chunks:
states.texture = &m_texture;
for (int ix = left; ix < right; ++ix)
{
for (int iy = top; iy < bottom; ++iy)
{
target.draw(m_chunks[ix][iy], states);
}
}
}
tile_atlas.h
#pragma once
#include <SFML/Graphics/Texture.hpp>
class tile_atlas
{
private:
sf::Texture atlas_texture;
public:
tile_atlas();
~tile_atlas();
sf::Texture& get_atlas();
};
tile_atlas.cpp
#include "tile_atlas.h"
#include <iostream>
#include <string>
tile_atlas::tile_atlas()
{
std::string file_string = "tilemap_test.png";
if (!atlas_texture.loadFromFile(file_string))
{
std::cout << "Failed loading file: " << file_string << std::endl;
exit(1);
}
}
tile_atlas::~tile_atlas()
{
}
sf::Texture& tile_atlas::get_atlas()
{
return atlas_texture;
}
I am trying to fix this code to remove vertical artifacts so the above image will always look like this no matter if moving the view or zooming in/out.
[code for answer]
Using #Mario's answer this is the code I wrote (at the bottom of main.cpp) that completely fixed the artifacts.
here is a great link showing an example.
https://www.sfml-dev.org/tutorials/2.5/graphics-draw.php#off-screen-drawing
#ifdef _DEBUG
app.clear();
#endif // DEBUG
//map.draw(app, dt);
/*-----------------------------------------------------------*/
// Draw the texture
//rt.clear();
rt.draw(map.at(0));
rt.display();
if (cam.getSize().x < 500)
{
rt.setSmooth(false);
}
else
{
rt.setSmooth(true);
}
//// get the target texture (where the stuff has been drawn)
const sf::Texture& texture = rt.getTexture();
sf::Sprite sprite(texture);
app.draw(sprite);
//app.draw(map.at(0));
/*-----------------------------------------------------------*/
app.display();
Simple, yet effective:
Render your pixels 1:1 without scaling to a render texture and then upscale that instead.
Might be a bit tricky to determine the correct position, zoom, etc. but it can be done.

Boost union does not work in 1.67 but works in 1.61. Why?

I am trying to work out why my code below works as expected in boost 1.61 but not in boost 1.67.
In boost 1.61 the input polygons are correctly combined and the outline polygon displayed.
In boost 1.67, with no code changes, the outline polygon is wrong and incomplete. It's as if it has missing points.
Output images added to help explain. There is also an #define BOOST_1_6_1 I needed to add as boost 1.61 does not seem to automatically add that header file.
Uncomment drawAllPolygons() if you want to see how the input polygons look.
Can anyone help?
#include <iostream>
#include <boost\geometry.hpp>
//#define BOOST_1_6_1
#ifdef BOOST_1_6_1
#include <boost/geometry/geometries/point_xy.hpp>
#endif
typedef boost::geometry::model::d2::point_xy<int> intPt;
typedef boost::geometry::model::polygon<intPt> polygon;
const int GRID_WIDTH = 220;
const int GRID_HEIGHT = 60;
bool canvas[GRID_WIDTH][GRID_HEIGHT];
std::vector<polygon> output;
std::vector<polygon> input;
void initCanvas()
{
for (unsigned int y = 0; y < GRID_HEIGHT; ++y)
{
for (unsigned int x = 0; x < GRID_WIDTH; ++x)
{
canvas[x][y] = false;
}
}
}
void drawGrid()
{
for (unsigned int y = 0; y < GRID_HEIGHT; ++y)
{
for (unsigned int x = 0; x < GRID_WIDTH; ++x)
{
if (canvas[x][y])
{
std::cout << "x";
}
else
{
std::cout << ".";
}
}
std::cout << std::endl;
}
}
polygon setupPolygon(const int startX, const int startY, const int width, const int height)
{
polygon polygon1;
int endX = startX + width;
int endY = startY + height;
for (int x = startX; x <= endX; ++x)
{
intPt pt(x, startY);
polygon1.outer().push_back(pt);
}
for (int y = startY; y <= endY; ++y)
{
intPt pt(endX, y);
polygon1.outer().push_back(pt);
}
for (int x = endX; x >= startX; --x)
{
intPt pt(x, endY);
polygon1.outer().push_back(pt);
}
for (int y = endY; y >= startY; --y)
{
intPt pt(startX, y);
polygon1.outer().push_back(pt);
}
return polygon1;
}
std::vector<polygon> combine(std::vector<polygon> input)
{
bool loop = true;
while (loop)
{
unsigned int before = input.size();
bool exit = false;
for (unsigned int i = 0; i < input.size() && !exit; ++i)
{
for (unsigned int j = i + 1; j < input.size() && !exit; ++j)
{
std::vector<polygon> output;
boost::geometry::correct(input[i]);
boost::geometry::correct(input[j]);
boost::geometry::union_(input[i], input[j], output);
if (i < j)
{
input.erase(input.begin() + j);
input.erase(input.begin() + i);
}
else
{
input.erase(input.begin() + i);
input.erase(input.begin() + j);
}
input.insert(input.begin(), output.begin(), output.end());
exit = true;
}
}
if (before == input.size())
{
loop = false;
}
}
return input;
}
void drawAllPolygons()
{
for (unsigned int i = 0; i < input.size(); ++i)
{
auto outer = input[i].outer();
for (unsigned int i = 0; i < outer.size(); ++i)
{
int x = outer[i].get<0>();
int y = outer[i].get<1>();
canvas[x][y] = true;
}
}
}
void drawCombinedPolygons()
{
for (unsigned int j = 0; j < output.size(); ++j)
{
auto outer = output[j].outer();
for (unsigned int i = 0; i < outer.size(); ++i)
{
int x = outer[i].get<0>();
int y = outer[i].get<1>();
canvas[x][y] = true;
}
}
}
int main()
{
initCanvas();
input.push_back(setupPolygon(40, 10, 50, 15));
input.push_back(setupPolygon(40, 20, 50, 15));
input.push_back(setupPolygon(50, 10, 50, 15));
input.push_back(setupPolygon(60, 30, 50, 15));
output = combine(input);
//drawAllPolygons();
drawCombinedPolygons();
drawGrid();
std::cin.get();
return 0;
}
Boost 1.61 apparently didn't do all the possible optimizations.
There are quite a few misconceptions.
The orientations for the input polygon (rings) are wrong. You can help your self using is_valid and correct. The code below will show it.
The original polygons are expressly created to contain many redundant points. Even if you just "union" one of those with themselves, you can expect the result to be simplified:
Live On Coliru
polygon p = setupPolygon(40, 10, 50, 15);
std::cout << "original: " << bg::wkt(p) << "\n";
multi_polygon simple;
bg::union_(p, p, simple);
std::cout << "self-union: " << bg::wkt(simple) << "\n";
Would print
original: POLYGON((40 10,40 11,40 12,40 13,40 14,40 15,40 16,40 17,40 18,40 19,40 20,40 21,40 22,40 23,40 24,40 25,40 25,41 25,42 25,43 25,44 25,45 25,46 25,47 25,48 25,49 25,50 25,51 25,52 25,53 25,54 25,55 25,56 25,57 25,58 25,59 25,60 25,61 25,62 25,63 25,64 25,65 25,66 25,67 25,68 25,69 25,70 25,71 25,72 25,73 25,74 25,75 25,76 25,77 25,78 25,79 25,80 25,81 25,82 25,83 25,84 25,85 25,86 25,87 25,88 25,89 25,90 25,90 25,90 24,90 23,90 22,90 21,90 20,90 19,90 18,90 17,90 16,90 15,90 14,90 13,90 12,90 11,90 10,90 10,89 10,88 10,87 10,86 10,85 10,84 10,83 10,82 10,81 10,80 10,79 10,78 10,77 10,76 10,75 10,74 10,73 10,72 10,71 10,70 10,69 10,68 10,67 10,66 10,65 10,64 10,63 10,62 10,61 10,60 10,59 10,58 10,57 10,56 10,55 10,54 10,53 10,52 10,51 10,50 10,49 10,48 10,47 10,46 10,45 10,44 10,43 10,42 10,41 10,40 10))
self-union: MULTIPOLYGON(((90 25,90 10,40 10,40 25,90 25)))
As you can see, the result will be simplified to just the obvious corners of the rectangle. However your "drawing" code relies on the redundant points to be present.
This is why you end up with "spotty" results - that seem to miss points, only because your drawing code doesn't draw any lines. It merely draws the (corner) points.
The canvas dimensions are inverted
I think you had your canvas indices consistently swapped around. I'd suggest using more explicit data-structures with (opional) bounds checking to avoid this.
Change to use SVG
If you change the code to output the SVG of the input and output, you will find that the whole thing makes perfect sense. Note that this also takes care to correct the input:
Live On Coliru
#include <iostream>
#include <fstream>
#include <boost/geometry.hpp>
#include <boost/geometry/geometries/point_xy.hpp>
namespace bg = boost::geometry;
typedef bg::model::d2::point_xy<int> intPt;
typedef bg::model::polygon<intPt> polygon;
typedef bg::model::multi_polygon<polygon> multi_polygon;
const int GRID_WIDTH = 220;
const int GRID_HEIGHT = 60;
std::array<std::array<bool, GRID_WIDTH>, GRID_HEIGHT> canvas;
void initCanvas() {
for (auto& row : canvas)
for (auto& cell : row)
cell = false;
}
void drawGrid() {
for (auto& row : canvas) {
for (auto& cell : row)
std::cout << (cell?"x":".");
std::cout << std::endl;
}
}
template <typename R> void valid(R& g) {
std::string reason;
while (!bg::is_valid(g, reason)) {
std::cout << "Not valid: " << reason << "\n";
bg::correct(g);
}
}
polygon setupPolygon(const int startX, const int startY, const int width, const int height) {
polygon::ring_type r;
int const endX = startX + width;
int const endY = startY + height;
for (int x = startX; x <= endX; ++x) r.emplace_back(x, startY);
for (int y = startY; y <= endY; ++y) r.emplace_back(endX, y);
for (int x = endX; x >= startX; --x) r.emplace_back(x, endY);
for (int y = endY; y >= startY; --y) r.emplace_back(startX, y);
valid(r);
return {r};
}
template <typename Polygons>
multi_polygon combine(Polygons const& input) {
if (input.empty()) return {};
//if (input.size()<2) return {input.front()};
multi_polygon out;
for (polygon const& i : input) {
multi_polygon tmp;
bg::union_(out, i, tmp);
out = tmp;
}
return out;
}
void draw(polygon const& p) {
for (auto& pt : p.outer())
canvas.at(pt.y()).at(pt.x()) = true;
}
void draw(multi_polygon const& mp) { for (auto& p : mp) draw(p); }
void draw(std::vector<polygon> const& mp) { for (auto& p : mp) draw(p); }
void writeSvg(multi_polygon const& g, std::string fname) {
std::ofstream svg(fname);
boost::geometry::svg_mapper<intPt> mapper(svg, 400, 400);
mapper.add(g);
mapper.map(g, "fill-opacity:0.5;fill:rgb(0,0,153);stroke:rgb(0,0,200);stroke-width:2");
}
void writeSvg(std::vector<polygon> const& g, std::string fname) {
std::ofstream svg(fname);
boost::geometry::svg_mapper<intPt> mapper(svg, 400, 400);
for (auto& p: g) {
mapper.add(p);
mapper.map(p, "fill-opacity:0.5;fill:rgb(153,0,0);stroke:rgb(200,0,0);stroke-width:2");
}
}
int main() {
std::vector<polygon> input { {
setupPolygon(40, 10, 50, 15),
setupPolygon(40, 20, 50, 15),
setupPolygon(50, 10, 50, 15),
setupPolygon(60, 30, 50, 15),
} };
for (auto& p : input)
valid(p);
auto output = combine(input);
initCanvas();
draw(input);
drawGrid();
initCanvas();
draw(output);
drawGrid();
writeSvg(input, "input.svg");
writeSvg(output, "output.svg");
}
Prints
Not valid: Geometry has wrong orientation
Not valid: Geometry has wrong orientation
Not valid: Geometry has wrong orientation
Not valid: Geometry has wrong orientation
............................................................................................................................................................................................................................
............................................................................................................................................................................................................................
............................................................................................................................................................................................................................
............................................................................................................................................................................................................................
............................................................................................................................................................................................................................
............................................................................................................................................................................................................................
............................................................................................................................................................................................................................
............................................................................................................................................................................................................................
............................................................................................................................................................................................................................
............................................................................................................................................................................................................................
........................................xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.......................................................................................................................
........................................x.........x.......................................x.........x.......................................................................................................................
........................................x.........x.......................................x.........x.......................................................................................................................
........................................x.........x.......................................x.........x.......................................................................................................................
[ snip ]
............................................................................................................................................................................................................................
............................................................................................................................................................................................................................
............................................................................................................................................................................................................................
And the input.svg:
And the output.svg:

Threaded Mandelbrot program C++

To preface this: I'm currently a first-year student who was allowed to enroll in some second-year classes. Because of this, I'm currently wrangling a language (C++) that I haven't really had the time to learn (First-years mostly learn C#), so this code might not be pretty.
Our assignment is twofold. First, we need to write a program that outputs a Mandelbrot image in a PPM. To achieve this, I've followed a Youtube tutorial here.
The second part of the assignment is to make the program multithreaded. Essentially, the program is supposed to use 4 threads that each draw a quarter of the image.
To this end I have altered the code from the video tutorial, and converted the main to a method. Now, I'm trying to properly make the first quarter of the image. I figured the way to do this was to adjust
for (int y = 0; y < imageHeight; y++) //Rows!
{
for (int x = 0; x < imageWidth; x++) //Columns! (pixels in every row)
{
to
for (int y = 0; y < halfHeight; y++) //Rows!
{
for (int x = 0; x < halfWidth; x++) //Columns! (pixels in every row)
{
However, instead of drawing the top left quarter as I suspected, the program drew along the full width, repeating itself after the halfway mark of the image width was reached, and only drew along a quarter of the height
(see image)
As I like to learn from my mistakes, I'd love to know what exactly is going wrong here.
Thank you for helping a programming greenhorn out :)
Full program code below.
#include "stdafx.h"
#include <fstream>
#include <iostream>
int imageWidth = 512, imageHeight = 512, maxN = 255, halfWidth = 256, halfHeight = 256;
double minR = -1.5, maxR = 0.7, minI = -1.0, maxI = 1.0;
std::ofstream f_out("output_image.ppm");
int findMandelbrot(double cr, double ci, int max_iterations)
{
int i = 0;
double zr = 0.0, zi = 0.0;
while (i < max_iterations && zr * zr + zi * zi < 4.0)
{
double temp = zr * zr - zi * zi + cr;
zi = 2.0 * zr * zi + ci;
zr = temp;
i++;
}
return i;
}
double mapToReal(int x, int imageWidth, double minR, double maxR)
{
double range = maxR - minR;
return x * (range / imageWidth) + minR;
}
double mapToImaginary(int y, int imageHeight, double minI, double maxI)
{
double range = maxI - minI;
return y * (range / imageHeight) + minI;
}
void threadedMandelbrot()
{
for (int y = 0; y < halfHeight; y++) //Rows!
{
for (int x = 0; x < halfWidth; x++) //Columns! (pixels in every row)
{
//... Find the real and imaginary values of c, corresponding
// to that x,y pixel in the image
double cr = mapToReal(x, imageWidth, minR, maxR);
double ci = mapToImaginary(y, imageHeight, minI, maxI);
//... Find the number of iterations in the Mandelbrot formula
// using said c.
int n = findMandelbrot(cr, ci, maxN);
//... Map the resulting number to an RGB value.
int r = (n % 256);
int g = (n % 256);
int b = (n % 256);
//... Output it to the image
f_out << r << " " << g << " " << b << " ";
}
f_out << std::endl;
}
}
int main()
{
//Initializes file
f_out << "P3" << std::endl;
f_out << imageWidth << " " << imageHeight << std::endl;
f_out << "256" << std::endl;
//For every pixel...
threadedMandelbrot();
f_out.close();
std::cout << "Helemaal klaar!" << std::endl;
return 0;
}
Your are calculating only a quarter of the image, so you have to set the dimension of that to halfHeight, halfWidth or fill the file with zeroes. When the image viewer reads the file, it shows two lines of it in a single line of pixels untill it reaches the end of the file, at a quarter of the picture height.
To fix the problem you just have to calculate the other three quarters of the image, but I suggest you to seperate the calc function from the file writing function: do the threaded calcs putting the result in an array (std::array or std::vector), look up the right color and then write to file.

C++ Rotation matrix issue when used on a square

I'm attempting to apply an a rotation matrix in C++ that rotates all points of square a specified degree around a specified origin. The catch is that it is based in the win32 console, so each point has to correspond with a pair of ints, rather than floating point values. As you can see below, the rotating square's overall shape is consistent with the desired result, but there are a number of 'holes' in it.
Here's my source code:
#include <iostream>
#include <cmath>
using namespace std;
enum {W = 50, H = 50, S = 25}; //Width, Height, Square size
struct Vector2i
{
int x;
int y;
Vector2i() {}
Vector2i(int _x, int _y) : x(_x), y(_y) {}
};
struct Square
{
bool Data[W][H];
Vector2i Origin = Vector2i(W / 2, H / 2);
void clear() {
for (int y = 0; y < H; ++y) {
for (int x = 0; x < W; ++x)
Data[x][y] = false;
}
}
void setSquare() {
for (int y = H / 2 - S / 2; y < H / 2 + S / 2; ++y) {
for (int x = W / 2 - S / 2; x < W / 2 + S / 2; ++x)
Data[x][y] = true;
}
}
void draw() {
for (int y = 0; y < H; ++y) {
for (int x = 0; x < W; ++x) {
if (y == Origin.y && x == Origin.x) std::cout << '+'; //Marks the origin
else if (Data[x][y]) std::cout << 'X';
else std::cout << '.';
}
std::cout << '\n';
}
}
};
Vector2i newPos(Vector2i old, double theta) {
theta *= 3.14159265d / 180.d; //Converting from degrees to radians
int X = ceil(cos(theta) * old.x - sin(theta) * old.y);
int Y = ceil(sin(theta) * old.x + cos(theta) * old.y);
return Vector2i(X, Y);
}
int main()
{
cout << "Enter an angle (in degrees): ";
double angle = 0;
cin >> angle;
Square One;
One.clear();
One.setSquare();
One.draw();
Square Two;
Two.clear();
///Draw the rotated square as the second square
for (int y = 0; y < H; ++y) {
for (int x = 0; x < W; ++x) {
if (One.Data[x][y]) {
Vector2i finalVec = newPos(Vector2i(x - One.Origin.x,
y - One.Origin.y), angle);
Two.Data[finalVec.x + One.Origin.x][finalVec.y + One.Origin.y] = true;
}
}
}
///Copy the second square back into the first
for (int y = 0; y < H; ++y) {
for (int x = 0; x < W; ++x)
One.Data[x][y] = Two.Data[x][y];
}
One.draw();
return 0;
}
Is this due to the accuracy of the newPos() function, or is it the rounding into int values that is causing this?
Additionally, is there a way to fix this or predict where the holes will be?
EDIT: SOLVED!
Going off of infgeoax's suggestion to work backwards, I created a function to calculate the original positions. I'll leave the augmented code here, in case anyone has a similar problem in the future (Thanks for all your help, everyone! [especially infgeoax, for the brainwave]):
#include <iostream>
#include <cmath>
using namespace std;
enum {W = 50, H = 50, S = 25};
struct Vector2i
{
int x;
int y;
Vector2i() {}
Vector2i(int _x, int _y) : x(_x), y(_y) {}
};
struct Square
{
bool Data[W][H];;
Vector2i Origin = Vector2i(W / 2, H / 2);
void clear() {
for (int y = 0; y < H; ++y) {
for (int x = 0; x < W; ++x)
Data[x][y] = false;
}
}
void setSquare() {
for (int y = H / 2 - S / 2; y < H / 2 + S / 2; ++y) {
for (int x = W / 2 - S / 2; x < W / 2 + S / 2; ++x)
Data[x][y] = true;
}
}
void draw() {
for (int y = 0; y < H; ++y) {
for (int x = 0; x < W; ++x) {
if (y == Origin.y && x == Origin.x) std::cout << '+'; //Marks the origin
else if (Data[x][y]) std::cout << 'X';
else std::cout << '.';
}
std::cout << '\n';
}
}
};
Vector2i oldPos(Vector2i new_, float theta) {
theta *= 3.14159265f / 180.f; //Converting from degrees to radians
return Vector2i(new_.x * cosf(theta) + new_.y * sinf(theta) + 0.5f,
new_.y * cosf(theta) - new_.x * sinf(theta) + 0.5f);
}
int main()
{
cout << "Enter an angle (in degrees): ";
float angle = 0;
cin >> angle;
Square One;
One.clear();
One.setSquare();
One.draw();
Square Two;
Two.clear();
for (int y = 0; y < H; ++y) {
for (int x = 0; x < W; ++x) {
Vector2i vec = oldPos(Vector2i(x - One.Origin.x, y - One.Origin.y), angle);
vec.x += One.Origin.x;
vec.y += One.Origin.y;
if (vec.x >= 0 && vec.x < W && vec.y >= 0 && vec.y < H)
Two.Data[x][y] = One.Data[vec.x][vec.y];
}
}
Two.draw();
return 0;
}
Well your problem has nothing to do with whether or not your are developing a console or GUI application. Images are stored and processed as matrices of pixels. When you rotate the image, the resulting position for a specific pixel is usually not integers.
The idea is to go the other way around.
You calculate the four corners of the rotated sqaure.
For each position(pixel) in the rotated square, you calculate its color by rotating it back to the original square.