PImage

Data / Composite

Description

Datatype for storing and manipulating images. Load with loadImage() or create with createImage().

Syntax

PImage* img = loadImage("file.png") PImage* img = createImage(w, h, ARGB)

Parameters

None

Returns

PImage

Methods

pixels[]Array of color values for the image
widthWidth of the image in pixels
heightHeight of the image in pixels
loadPixels()Load pixel data into pixels[]
updatePixels()Update image from pixels[]
get(int x, int y)Get pixel color at position
set(int x, int y, color c)Set pixel color at position
resize(int w, int h)Resize the image
filter(int mode)Apply an image filter
mask(PImage m)Apply an alpha mask

Related

Under the Hood

From Processing.h:

class PImage { public: int width = 0; int height = 0; std::vector<unsigned int> pixels; GLuint texID = 0; bool dirty = false; PImage() = default; PImage(int w, int h) { // Guard against bad dimensions from corrupted files or failed loads if (w > 0 && h > 0 && w < 16384 && h < 16384) { width = w; height = h; pixels.assign((size_t)w * h, 0xFF000000); } } // Pixel read/write (bounds-checked) unsigned int get(int x, int y) const { if (x<0||x>=width||y<0||y>=height) return 0; return pixels[y*width+x]; } void set(int x, int y, unsigned int c) { if (x<0||x>=width||y<0||y>=height) return; pixels[y*width+x] = c; dirty = true; } // These mirror the Processing Java API; dirty flag is used by updatePixels() void loadPixels() {} void updatePixels() { dirty = true; } // Upload CPU pixels to the GPU texture void uploadTexture(); // defined in Processing.cpp void resize(int w, int h) { width=w; height=h; pixels.assign(w*h, 0xFF000000); dirty=true; } // Apply an image filter to all pixels void filter(int mode) { for (auto& p : pixels) { int r=(p>>16)&0xFF, g=(p>>8)&0xFF, b=p&0xFF, a=(p>>24)&0xFF; if (mode == GRAY) { int gr=(r+g+b)/3; p=(a<<24)|(gr<<16)|(gr<<8)|gr; } else if (mode == INVERT) { p=(a<<24)|((255-r)<<16)|((255-g)<<8)|(255-b); } else if (mode == THRESHOLD) { int gr=(r+g+b)/3; int t=gr>127?255:0; p=(a<<24)|(t<<16)|(t<<8)|t; } } dirty = true; } // Extract a sub-image PImage get(int x, int y, int w, int h) const { PImage out(w, h); for (int iy=0; iy<h; iy++) for (int ix=0; ix<w; ix++) out.pixels[iy*w+ix] = get(x+ix, y+iy); return out; } // Copy from another image with scaling void copy(const PImage& src, int sx, int sy, int sw, int sh, int dx, int dy, int dw, int dh) { for (int iy=0; iy<dh; iy++) for (int ix=0; ix<dw; ix++) { int srcX = sx+(int)(ix*(float)sw/dw); int srcY = sy+(int)(iy*(float)sh/dh); set(dx+ix, dy+iy, src.get(srcX, srcY)); } dirty = true; } // Apply alpha mask from another grayscale image void mask(const PImage& m) { for (int i=0; i<width*height && i<(int)m.pixels.size(); i++) { int a = (m.pixels[i]>>16)&0xFF; pixels[i] = (pixels[i]&0x00FFFFFF)|(a<<24); } dirty = true; } void mask(const PImage* m) { if (m) mask(*m); } // Destructor frees GPU texture virtual ~PImage() { if (texID) glDeleteTextures(1, &texID); } // Non-copyable (owns GPU resource -- use PImage* for assignment) PImage(const PImage&) __attribute__((error( "E0002: PImage value-style copying is not supported. " "Declare PImage* instead of PImage. " "See https://processing-cpp.github.io/error/E0002.html" ))); PImage& operator=(const PImage&) __attribute__((error( "E0002: PImage value-style assignment is not supported. " "Declare PImage* instead of PImage. " "See https://processing-cpp.github.io/error/E0002.html" ))); // Movable PImage(PImage&& o) noexcept : width(o.width), height(o.height), pixels(std::move(o.pixels)), texID(o.texID), dirty(o.dirty) { o.texID=0; } }; PGraphics(int w, int h) : PImage(w, h) { // Can't reach PApplet::g_papplet here -- PApplet's complete type // isn't available yet at this point in the header (PGraphics is // defined before it). Default directly to real Processing's own // P2D/P3D default (smooth(2)) rather than reaching across that // forward-reference gap. A sketch wanting a different level for // its buffers can extend this later if needed. samples = 0; // TEMPORARY: forced off to test if MSAA itself is the bug // Resolve target: plain, non-multisampled FBO + texture -- // unchanged from before, just filled via a blit-resolve now. glGenFramebuffers(1, &fbo); glBindFramebuffer(GL_FRAMEBUFFER, fbo); if (texID == 0) glGenTextures(1, &texID); glBindTexture(GL_TEXTURE_2D, texID); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texID, 0); glGenRenderbuffers(1, &rbo); glBindRenderbuffer(GL_RENDERBUFFER, rbo); glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, w, h); glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rbo); glBindFramebuffer(GL_FRAMEBUFFER, 0); // Multisample render target: only created if antialiasing was // actually requested. beginDraw() binds THIS one; endDraw() blits // it down into the resolve target above. if (samples > 0) { glGenFramebuffers(1, &msaaFbo); glBindFramebuffer(GL_FRAMEBUFFER, msaaFbo); glGenRenderbuffers(1, &msaaColorRbo); glBindRenderbuffer(GL_RENDERBUFFER, msaaColorRbo); glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples, GL_RGBA8, w, h); glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, msaaColorRbo); glGenRenderbuffers(1, &msaaDepthRbo); glBindRenderbuffer(GL_RENDERBUFFER, msaaDepthRbo); glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples, GL_DEPTH24_STENCIL8, w, h); glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, msaaDepthRbo); GLenum msaaStatus = glCheckFramebufferStatus(GL_FRAMEBUFFER); if (msaaStatus != GL_FRAMEBUFFER_COMPLETE) { glDeleteFramebuffers(1, &msaaFbo); msaaFbo = 0; glDeleteRenderbuffers(1, &msaaColorRbo); msaaColorRbo = 0; glDeleteRenderbuffers(1, &msaaDepthRbo); msaaDepthRbo = 0; samples = 0; } glBindFramebuffer(GL_FRAMEBUFFER, 0); } }

Under the Hood

From Processing.cpp:

return new PImage();