Home
About
Donate
This project contains the following files (right-click files you'd like to download):
image.cpptestimages.zip
//[header] // Simple image manipulations //[/header] //[compile] // Download the image.cpp and test images (zip file) to a folder. // Open a shell/terminal, and run the following command where the files is saved: // // clang++ -o image image.cpp -std=c++11 -O3 // // You can use c++ if you don't use clang++ // // Run with: ./image. Open the resulting image (ppm) in Photoshop or any program // reading PPM files. //[/compile] //[ignore] // Copyright (C) 2016 www.scratchapixel.com // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see <http://www.gnu.org/licenses/>. //[/ignore] #include <cstdlib> #include <cstdio> #include <cstring> #include <algorithm> #include <fstream> #include <cassert> #include <exception> #include <vector> // [comment] // The main Image class // [/comment] class Image { public: struct Rgb { Rgb() : r(0), g(0), b(0) {} Rgb(float c) : r(c), g(c), b(c) {} Rgb(float _r, float _g, float _b) : r(_r), g(_g), b(_b) {} bool operator != (const Rgb &c) const { return c.r != r && c.g != g && c.b != b; } Rgb& operator *= (const Rgb &rgb) { r *= rgb.r, g *= rgb.g, b *= rgb.b; return *this; } Rgb& operator += (const Rgb &rgb) { r += rgb.r, g += rgb.g, b += rgb.b; return *this; } friend float& operator += (float &f, const Rgb rgb) { f += (rgb.r + rgb.g + rgb.b) / 3.f; return f; } float r, g, b; }; Image() : w(0), h(0), pixels(nullptr) { /* empty image */ } Image(const unsigned int &_w, const unsigned int &_h, const Rgb &c = kBlack) : w(_w), h(_h), pixels(nullptr) { pixels = new Rgb[w * h]; for (int i = 0; i < w * h; ++i) pixels[i] = c; } Image(const Image &img) : w(img.w), h(img.h), pixels(nullptr) { pixels = new Rgb[w * h]; memcpy(pixels, img.pixels, sizeof(Rgb) * w * h); } // move constructor Image(Image &&img) : w(0), h(0), pixels(nullptr) { w = img.w; h = img.h; pixels = img.pixels; img.pixels = nullptr; img.w = img.h = 0; } // move assignment operator Image& operator = (Image &&img) { if (this != &img) { if (pixels != nullptr) delete [] pixels; w = img.w, h = img.h; pixels = img.pixels; img.pixels = nullptr; img.w = img.h = 0; } return *this; } Rgb& operator () (const unsigned &x, const unsigned int &y) const { assert(x < w && y < h); return pixels[y * w + x]; } Image& operator *= (const Rgb &rgb) { for (int i = 0; i < w * h; ++i) pixels[i] *= rgb; return *this; } Image& operator += (const Image &img) { for (int i = 0; i < w * h; ++i) pixels[i] += img[i]; return *this; } Image& operator /= (const float &div) { float invDiv = 1 / div; for (int i = 0; i < w * h; ++i) pixels[i] *= invDiv; return *this; } friend Image operator * (const Rgb &rgb, const Image &img) { Image tmp(img); tmp *= rgb; return tmp; } Image operator * (const Image &img) { Image tmp(*this); // multiply pixels together for (int i = 0; i < w * h; ++i) tmp[i] *= img[i]; return tmp; } static Image circshift(const Image &img, const std::pair<int,int> &shift) { Image tmp(img.w, img.h); int w = img.w, h = img.h; for (int j = 0; j < h; ++j) { int jmod = (j + shift.second) % h; for (int i = 0; i < w; ++i) { int imod = (i + shift.first) % w; tmp[jmod * w + imod] = img[j * w + i]; } } return tmp; } const Rgb& operator [] (const unsigned int &i) const { return pixels[i]; } Rgb& operator [] (const unsigned int &i) { return pixels[i]; } ~Image() { if (pixels != nullptr) delete [] pixels; } unsigned int w, h; Rgb *pixels; static const Rgb kBlack, kWhite, kRed, kGreen, kBlue; }; const Image::Rgb Image::kBlack = Image::Rgb(0); const Image::Rgb Image::kWhite = Image::Rgb(1); const Image::Rgb Image::kRed = Image::Rgb(1,0,0); const Image::Rgb Image::kGreen = Image::Rgb(0,1,0); const Image::Rgb Image::kBlue = Image::Rgb(0,0,1); // [comment] // Save an image to PPM image file // [/comment] void savePPM(const Image &img, const char *filename) { if (img.w == 0 || img.h == 0) { fprintf(stderr, "Can't save an empty image\n"); return; } std::ofstream ofs; try { ofs.open(filename, std::ios::binary); // need to spec. binary mode for Windows users if (ofs.fail()) throw("Can't open output file"); ofs << "P6\n" << img.w << " " << img.h << "\n255\n"; unsigned char r, g, b; // loop over each pixel in the image, clamp and convert to byte format for (int i = 0; i < img.w * img.h; ++i) { r = static_cast<unsigned char>(std::min(1.f, img.pixels[i].r) * 255); g = static_cast<unsigned char>(std::min(1.f, img.pixels[i].g) * 255); b = static_cast<unsigned char>(std::min(1.f, img.pixels[i].b) * 255); ofs << r << g << b; } ofs.close(); } catch (const char *err) { fprintf(stderr, "%s\n", err); ofs.close(); } } // [comment] // Read a PPM image file // [/comment] Image readPPM(const char *filename) { std::ifstream ifs; ifs.open(filename, std::ios::binary); // need to spec. binary mode for Windows users Image img; try { if (ifs.fail()) { throw("Can't open input file"); } std::string header; int w, h, b; ifs >> header; if (strcmp(header.c_str(), "P6") != 0) throw("Can't read input file"); ifs >> w >> h >> b; img.w = w; img.h = h; img.pixels = new Image::Rgb[w * h]; // this is throw an exception if bad_alloc ifs.ignore(256, '\n'); // skip empty lines in necessary until we get to the binary data unsigned char pix[3]; // read each pixel one by one and convert bytes to floats for (int i = 0; i < w * h; ++i) { ifs.read(reinterpret_cast<char *>(pix), 3); img.pixels[i].r = pix[0] / 255.f; img.pixels[i].g = pix[1] / 255.f; img.pixels[i].b = pix[2] / 255.f; // [comment] // This is just to make the bokeh effect more visible, book high value pixels brightness // [/comment] if (img.pixels[i].r > 0.7) img.pixels[i].r *= 3; if (img.pixels[i].g > 0.7) img.pixels[i].g *= 3; if (img.pixels[i].b > 0.7) img.pixels[i].b *= 3; } ifs.close(); } catch (const char *err) { fprintf(stderr, "%s\n", err); ifs.close(); } return img; } // [comment] // Simulate the bokeh effect (downalod the test images or create your own) // [/comment] int main(int argc, char **argv) { try { Image I = readPPM("./xmas.ppm"); Image J = readPPM("./heart.ppm"); int w = J.w, h = J.h; Image K(w, h); float total = 0; for (int j = 0; j < h; ++j) { for (int i = 0; i < w; ++i) { if (J(i, j) != Image::kBlack) { K += J(i, j) * Image::circshift(I, std::pair<int, int>(i, j)); total += J(i, j); } } } K /= total; savePPM(K, "./out.ppm"); } catch (const std::exception &e) { // catch general exception (bad_alloc mainly?) fprintf(stderr, "Error: %s\n", e.what()); } return 0; }