/* Cellular Texture generator. (c) Jarmo 2008-2013. http://z0b.kapsi.fi Public domain. No warranty of any kind. Use strictly at your own risk. Compilation example: g++ -s -O2 -Wall -o cellular[.exe] cellular.cpp -lpng -lz */ #include #include #include #include #include #include #include #include #include using namespace std; // saves a PNG image, 'bpp' (bytes per pixel) must be either 3 (RGB) or 4 (RGBA), returns false if fails bool savePNG(const char *fileName, const uint32_t w, const uint32_t h, const uint8_t bpp, const uint8_t *data) { // sanity check if (!fileName || !data || w < 1 || h < 1 || (bpp != 3 && bpp != 4)) return false; FILE *f = fopen(fileName, "wb"); if (!f) return false; // init structures png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); if (!png_ptr) { fclose(f); return false; } png_infop info_ptr = png_create_info_struct(png_ptr); if (!info_ptr) { png_destroy_write_struct(&png_ptr, NULL); fclose(f); return false; } // setup error handling if (setjmp(png_jmpbuf(png_ptr))) { png_destroy_write_struct(&png_ptr, &info_ptr); fclose(f); return false; } // init output png_init_io(png_ptr, f); // this is optional, set the zlib compression level png_set_compression_level(png_ptr, Z_BEST_COMPRESSION); // set image properties png_set_IHDR(png_ptr, info_ptr, w, h, 8, bpp == 3 ? PNG_COLOR_TYPE_RGB : PNG_COLOR_TYPE_RGBA, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); // write the image data (one scanline at a time, it can be customized easily // and we don't have to mess with row pointers) png_write_info(png_ptr, info_ptr); const uint32_t stride = w * bpp; png_byte *row = (png_byte *)data; for (uint32_t y = 0; y < h; y++) { png_write_row(png_ptr, row); row += stride; } // we're done png_write_end(png_ptr, info_ptr); png_destroy_write_struct(&png_ptr, &info_ptr); fclose(f); return true; } struct point_t { float x, y; float r, g, b; }; // How many points there will be. Needs to be at least two. static const size_t kNumPoints = 128; // size of the generated image static const int32_t kWidth = 512, kHeight = 512; // the points themselves static point_t points[kNumPoints]; // index of the nearest point from every pixel in the image static vector nearestIndex(kWidth * kHeight); // distance to the closest point from every pixel in the image static vector distances(kWidth * kHeight); // final generated RGB image static vector image; // Draws the points. The pixel color can be changed, by default it is white. static void showPoints(const uint8_t r = 255, const uint8_t g = 255, const uint8_t b = 255) { for (size_t i = 0; i < kNumPoints; i++) { uint8_t *pixel = &image[((int)points[i].y * kWidth + (int)points[i].x) * 3]; *pixel++ = r; *pixel++ = g; *pixel++ = b; } } // Wraps the coordinate. This is what makes the output tileable. static void wrap(float &f, const float max) { if (f > max / 2.0f) f = max - f; } // just shows the distance buffer static void drawNearest() { uint8_t *p = &image[0]; for (int32_t y = 0, z = 0; y < kHeight; y++) for (int32_t x = 0; x < kWidth; x++) { const uint8_t c = (uint8_t)(distances[z++] * 255.0f); *p++ = c; *p++ = c; *p++ = c; } } // just shows the distance buffer, but mixes it with the points' colors static void drawNearestColored() { uint8_t *p = &image[0]; for (int32_t y = 0, z = 0; y < kHeight; y++) for (int32_t x = 0; x < kWidth; x++) { *p++ = (uint8_t)(points[nearestIndex[z]].r * distances[z] * 255.0f); *p++ = (uint8_t)(points[nearestIndex[z]].g * distances[z] * 255.0f); *p++ = (uint8_t)(points[nearestIndex[z]].b * distances[z] * 255.0f); z++; } } // draws solid Voronoi cells, takes their colors from the points static void drawSolidVoronoi() { uint8_t *p = &image[0]; for (int32_t y = 0, z = 0; y < kHeight; y++) for (int32_t x = 0; x < kWidth; x++) { float r = points[nearestIndex[z]].r, g = points[nearestIndex[z]].g, b = points[nearestIndex[z]].b; *p++ = (uint8_t)(r * 255.0f); *p++ = (uint8_t)(g * 255.0f); *p++ = (uint8_t)(b * 255.0f); z++; } } // draws Voronoi cells, takes their colors from the points and mixes them with // the distance buffer static void drawFadedVoronoi() { uint8_t *p = &image[0]; for (int32_t y = 0, z = 0; y < kHeight; y++) for (int32_t x = 0; x < kWidth; x++) { float r = points[nearestIndex[z]].r * distances[z], g = points[nearestIndex[z]].g * distances[z], b = points[nearestIndex[z]].b * distances[z]; *p++ = (uint8_t)(r * 255.0f); *p++ = (uint8_t)(g * 255.0f); *p++ = (uint8_t)(b * 255.0f); z++; } } int main() { // save this and you'll essentially save the whole image in a single number const int32_t seed = time(0); srand(seed); printf("Rendering (seed=%d)\n", seed); image.resize(kWidth * kHeight * 3); // generate the points for (size_t i = 0; i < kNumPoints; i++) { // random position points[i].x = (float)(rand() % kWidth); points[i].y = (float)(rand() % kHeight); // random RGB color points[i].r = (rand() % 255) / 255.0f; points[i].g = (rand() % 255) / 255.0f; points[i].b = (rand() % 255) / 255.0f; } // generate the distance and index arrays float minDist = numeric_limits::max(), maxDist = numeric_limits::min(); for (int32_t y = 0, z = 0; y < kHeight; y++) for (int32_t x = 0; x < kWidth; x++) { float dist[kNumPoints]; float dist1 = numeric_limits::max(), dist2 = numeric_limits::max(); for (size_t i = 0; i < kNumPoints; i++) { float dx = fabs(x - points[i].x), dy = fabs(y - points[i].y); wrap(dx, kWidth); wrap(dy, kHeight); dist[i] = dx * dx + dy * dy; // find the two closest points if (dist[i] < dist2) { if (dist[i] < dist1) { dist2 = dist1; dist1 = dist[i]; nearestIndex[z] = i; } else dist2 = dist[i]; } } /* The magic happens here. The next line computes the final distance value. Different patterns can be achieved by using 'dist1' and/or 'dist2' creatively. Try these: - dist1 (looks very organic, almost like a growth of some kind) - dist2 (crystal-like pattern) - dist1 * dist1 (Blobs) - dist2 - dist1 (Voronoi pattern) */ float final = sqrtf(dist2 - dist1); // keep track of the distances for later normalization minDist = min(minDist, final); maxDist = max(maxDist, final); distances[z] = final; z++; } // the distances must be normalized before they can be used for (vector::iterator i = distances.begin(); i != distances.end(); ++i) { *i -= minDist; *i /= maxDist - minDist; } // drawNearest(); drawSolidVoronoi(); // drawFadedVoronoi(); // showPoints(255, 0, 0); const char *fname = "cellular.png"; printf("Saving \"%s\"...\n", fname); if (!savePNG(fname, kWidth, kHeight, 3, &image[0])) printf("PNG save failed\n"); return 0; }