/* PRACTICAL PORTION OF COMMENTS: ============================== THIS CODE DOES NOT WORK AS IS Sorry. I don't have the resources to recompile the entire core, and too much of PImage is private and/or static for me to actually derive a workable subclass. So this is effectively just PSEUDO-CODE. I've made all the necessary changes in appropriate places, but I have no way to test them. Look for such sections of code blocked off with the following comment style: // <-- davbol 2006-12-16 ..changed code in here... // davbol 2006-12-16 --> RHETORICAL PORTION OF COMMENTS: =============================== why are the blend modes defined as bitmasks? are we supposed to be able to OR them together and get multiple blends simultaneously? and if so, what does that mean?? f.e. can I call blendMode(ADD | SUBTRACT); ?! if not, then they'd be more efficiently stored just as sequential int's, right? no biggie, just struck me as unusual NONE OF THE CONTRIBUTED BLEND OPERATIONS ARE OPTIMIZED IN TERMS OF MASK/SHIFT OPERATIONS but at least the implementation stubs are all there, optimize later note re: formula docs: if i assume that toxi's use of "a" "b" and "f" were intended to correspond to porter-duff's "A" "B" and "Fb", then the mix() routine is equivalent to the "B OVER A" operation. i would have expected the "A OVER B" operation, but mathematically they're the same if you just swap variable names, so who cares?: the only important thing is that you keep them straight, since some operations require specific ordering of the layers. unlike DIFFERENCE, for example, where it doesn't matter at all. so, if I doc a formula as C = A - B then i'm using "B OVER A" notation to match p5's variables, but would be a different mathematical result under "A OVER B" notation, so B is the overlay, A is the underlay. ("A OVER B" has the slight advantage of being more easily memorable with the gimmick "Above OVER Below", but no matter) it is TAKEN AS A GIVEN that all formula will go through the "B OVER A" alpha blending operation, so that math is NOT given in the formula for C, unlike the current comments above blendColor. also note that the current doc for SUBTRACT is backwords - follow the math, its A-B not B-A as commented. sorry if i sound like i'm nitpicking, but the details are important if you want to get this all straightened out. So, if it were me, I'd document SUBTRACT as: C = A - B (in a B OVER A operation) The reason such documentation works is that you assume Fb=1.0 then you don't need to show the A+((B-A)*F) alpha blending, it is IMPLIED that the alpha blending ALWAYS occurs. So I'm just going to document the portion that changes, the actual blend operator. is that dead horse beaten enough? geez, sorry for ranting. a good source of formulae appears to be: http://www.simpelfilter.de/en/grundlagen/mixmods.html (note however that their formulae are given in A OVER B notation) */ class PImage_WithNewBlendModes extends PImage { public new_PImage() { super(); } public new_PImage(int width, int height) { super(width,height,RGB); } public new_PImage(int width, int height, int format) { super(width,height,format); } // REPLICATING & BLENDING (AREAS) OF PIXELS /** * Blend a two colors based on a particular mode. *
   * BLEND - linear interpolation of colours: C = A*factor + B
   * ADD - additive blending with white clip: C = min(A*factor + B, 255)
   * SUBTRACT - substractive blend with black clip: C = max(B - A*factor, 0)
   * DARKEST - only the darkest colour succeeds: C = min(A*factor, B)
   * LIGHTEST - only the lightest colour succeeds: C = max(A*factor, B)
   * REPLACE - destination colour equals colour of source pixel: C = A
   * 
*/ public int blendColor(int c1, int c2, int mode) { switch (mode) { // <-- davbol 2006-12-16 renamed old blend_multiply() to blend_blend() case BLEND: return blend_blend(c1, c2); // davbol 2006-12-16 --> case ADD: return blend_add_pin(c1, c2); case SUBTRACT: return blend_sub_pin(c1, c2); case LIGHTEST: return blend_lightest(c1, c2); case DARKEST: return blend_darkest(c1, c2); case REPLACE: return c2; // <-- davbol 2006-12-16 case DIFFERENCE : return blend_difference(c1, c2); case MULTIPLY : return blend_multiply(c1, c2); case SCREEN : return blend_screen(c1, c2); case OVERLAY : return blend_overlay(c1, c2); case HARD_LIGHT : return blend_hard_light(c1, c2); case SOFT_LIGHT : return blend_soft_light(c1, c2); // davbol 2006-12-16 --> } return 0; } /** * Internal blitter/resizer/copier from toxi. * Uses bilinear filtering if smooth() has been enabled * 'mode' determines the blending mode used in the process. */ private void blit_resize(new_PImage img, int srcX1, int srcY1, int srcX2, int srcY2, int[] destPixels, int screenW, int screenH, int destX1, int destY1, int destX2, int destY2, int mode) { if (srcX1 < 0) srcX1 = 0; if (srcY1 < 0) srcY1 = 0; if (srcX2 >= img.width) srcX2 = img.width - 1; if (srcY2 >= img.height) srcY2 = img.height - 1; int srcW = srcX2 - srcX1; int srcH = srcY2 - srcY1; int destW = destX2 - destX1; int destH = destY2 - destY1; if (!smooth) { srcW++; srcH++; } if (destW <= 0 || destH <= 0 || srcW <= 0 || srcH <= 0 || destX1 >= screenW || destY1 >= screenH || srcX1 >= img.width || srcY1 >= img.height) { return; } int dx = (int) (srcW / (float) destW * PRECISIONF); int dy = (int) (srcH / (float) destH * PRECISIONF); srcXOffset = (int) (destX1 < 0 ? -destX1 * dx : srcX1 * PRECISIONF); srcYOffset = (int) (destY1 < 0 ? -destY1 * dy : srcY1 * PRECISIONF); if (destX1 < 0) { destW += destX1; destX1 = 0; } if (destY1 < 0) { destH += destY1; destY1 = 0; } destW = low(destW, screenW - destX1); destH = low(destH, screenH - destY1); int destOffset = destY1 * screenW + destX1; srcBuffer = img.pixels; if (smooth) { // use bilinear filtering iw = img.width; iw1 = img.width - 1; ih1 = img.height - 1; switch (mode) { case BLEND: for (int y = 0; y < destH; y++) { filter_new_scanline(); for (int x = 0; x < destW; x++) { destPixels[destOffset + x] = // <-- davbol 2006-12-16 renamed old blend_multiply() to blend_blend() blend_blend(destPixels[destOffset + x], filter_bilinear()); // davbol 2006-12-16 --> sX += dx; } destOffset += screenW; srcYOffset += dy; } break; case ADD: for (int y = 0; y < destH; y++) { filter_new_scanline(); for (int x = 0; x < destW; x++) { destPixels[destOffset + x] = blend_add_pin(destPixels[destOffset + x], filter_bilinear()); sX += dx; } destOffset += screenW; srcYOffset += dy; } break; case SUBTRACT: for (int y = 0; y < destH; y++) { filter_new_scanline(); for (int x = 0; x < destW; x++) { destPixels[destOffset + x] = blend_sub_pin(destPixels[destOffset + x], filter_bilinear()); sX += dx; } destOffset += screenW; srcYOffset += dy; } break; case LIGHTEST: for (int y = 0; y < destH; y++) { filter_new_scanline(); for (int x = 0; x < destW; x++) { destPixels[destOffset + x] = blend_lightest(destPixels[destOffset + x], filter_bilinear()); sX += dx; } destOffset += screenW; srcYOffset += dy; } break; case DARKEST: for (int y = 0; y < destH; y++) { filter_new_scanline(); for (int x = 0; x < destW; x++) { destPixels[destOffset + x] = blend_darkest(destPixels[destOffset + x], filter_bilinear()); sX += dx; } destOffset += screenW; srcYOffset += dy; } break; case REPLACE: for (int y = 0; y < destH; y++) { filter_new_scanline(); for (int x = 0; x < destW; x++) { destPixels[destOffset + x] = filter_bilinear(); sX += dx; } destOffset += screenW; srcYOffset += dy; } break; // <-- davbol 2006-12-16 case DIFFERENCE: for (int y = 0; y < destH; y++) { filter_new_scanline(); for (int x = 0; x < destW; x++) { destPixels[destOffset + x] = blend_difference(destPixels[destOffset + x], filter_bilinear()); sX += dx; } destOffset += screenW; srcYOffset += dy; } break; case MULTIPLY: for (int y = 0; y < destH; y++) { filter_new_scanline(); for (int x = 0; x < destW; x++) { destPixels[destOffset + x] = blend_multiply(destPixels[destOffset + x], filter_bilinear()); sX += dx; } destOffset += screenW; srcYOffset += dy; } break; case SCREEN: for (int y = 0; y < destH; y++) { filter_new_scanline(); for (int x = 0; x < destW; x++) { destPixels[destOffset + x] = blend_screen(destPixels[destOffset + x], filter_bilinear()); sX += dx; } destOffset += screenW; srcYOffset += dy; } break; case OVERLAY: for (int y = 0; y < destH; y++) { filter_new_scanline(); for (int x = 0; x < destW; x++) { destPixels[destOffset + x] = blend_overlay(destPixels[destOffset + x], filter_bilinear()); sX += dx; } destOffset += screenW; srcYOffset += dy; } break; case HARD_LIGHT: for (int y = 0; y < destH; y++) { filter_new_scanline(); for (int x = 0; x < destW; x++) { destPixels[destOffset + x] = blend_hard_light(destPixels[destOffset + x], filter_bilinear()); sX += dx; } destOffset += screenW; srcYOffset += dy; } break; case SOFT_LIGHT: for (int y = 0; y < destH; y++) { filter_new_scanline(); for (int x = 0; x < destW; x++) { destPixels[destOffset + x] = blend_soft_light(destPixels[destOffset + x], filter_bilinear()); sX += dx; } destOffset += screenW; srcYOffset += dy; } break; // davebol 2006-12-16 --> } } else { // nearest neighbour scaling (++fast!) switch (mode) { case BLEND: for (int y = 0; y < destH; y++) { sX = srcXOffset; sY = (srcYOffset >> PRECISIONB) * img.width; for (int x = 0; x < destW; x++) { destPixels[destOffset + x] = // <-- davbol 2006-12-16 renamed old blend_multiply() to blend_blend() blend_blend(destPixels[destOffset + x], srcBuffer[sY + (sX >> PRECISIONB)]); // davbol 2006-12-16 --> sX += dx; } destOffset += screenW; srcYOffset += dy; } break; case ADD: for (int y = 0; y < destH; y++) { sX = srcXOffset; sY = (srcYOffset >> PRECISIONB) * img.width; for (int x = 0; x < destW; x++) { destPixels[destOffset + x] = blend_add_pin(destPixels[destOffset + x], srcBuffer[sY + (sX >> PRECISIONB)]); sX += dx; } destOffset += screenW; srcYOffset += dy; } break; case SUBTRACT: for (int y = 0; y < destH; y++) { sX = srcXOffset; sY = (srcYOffset >> PRECISIONB) * img.width; for (int x = 0; x < destW; x++) { destPixels[destOffset + x] = blend_sub_pin(destPixels[destOffset + x], srcBuffer[sY + (sX >> PRECISIONB)]); sX += dx; } destOffset += screenW; srcYOffset += dy; } break; case LIGHTEST: for (int y = 0; y < destH; y++) { sX = srcXOffset; sY = (srcYOffset >> PRECISIONB) * img.width; for (int x = 0; x < destW; x++) { destPixels[destOffset + x] = blend_lightest(destPixels[destOffset + x], srcBuffer[sY + (sX >> PRECISIONB)]); sX += dx; } destOffset += screenW; srcYOffset += dy; } break; case DARKEST: for (int y = 0; y < destH; y++) { sX = srcXOffset; sY = (srcYOffset >> PRECISIONB) * img.width; for (int x = 0; x < destW; x++) { destPixels[destOffset + x] = blend_darkest(destPixels[destOffset + x], srcBuffer[sY + (sX >> PRECISIONB)]); sX += dx; } destOffset += screenW; srcYOffset += dy; } break; case REPLACE: for (int y = 0; y < destH; y++) { sX = srcXOffset; sY = (srcYOffset >> PRECISIONB) * img.width; for (int x = 0; x < destW; x++) { destPixels[destOffset + x] = srcBuffer[sY + (sX >> PRECISIONB)]; sX += dx; } destOffset += screenW; srcYOffset += dy; } break; // <-- davbol 2006-12-16 case DIFFERENCE: for (int y = 0; y < destH; y++) { sX = srcXOffset; sY = (srcYOffset >> PRECISIONB) * img.width; for (int x = 0; x < destW; x++) { destPixels[destOffset + x] = blend_difference(destPixels[destOffset + x], srcBuffer[sY + (sX >> PRECISIONB)]); sX += dx; } destOffset += screenW; srcYOffset += dy; } break; case MULTIPLY: for (int y = 0; y < destH; y++) { sX = srcXOffset; sY = (srcYOffset >> PRECISIONB) * img.width; for (int x = 0; x < destW; x++) { destPixels[destOffset + x] = blend_multiply(destPixels[destOffset + x], srcBuffer[sY + (sX >> PRECISIONB)]); sX += dx; } destOffset += screenW; srcYOffset += dy; } break; case SCREEN: for (int y = 0; y < destH; y++) { sX = srcXOffset; sY = (srcYOffset >> PRECISIONB) * img.width; for (int x = 0; x < destW; x++) { destPixels[destOffset + x] = blend_screen(destPixels[destOffset + x], srcBuffer[sY + (sX >> PRECISIONB)]); sX += dx; } destOffset += screenW; srcYOffset += dy; } break; case OVERLAY: for (int y = 0; y < destH; y++) { sX = srcXOffset; sY = (srcYOffset >> PRECISIONB) * img.width; for (int x = 0; x < destW; x++) { destPixels[destOffset + x] = blend_overlay(destPixels[destOffset + x], srcBuffer[sY + (sX >> PRECISIONB)]); sX += dx; } destOffset += screenW; srcYOffset += dy; } break; case HARD_LIGHT: for (int y = 0; y < destH; y++) { sX = srcXOffset; sY = (srcYOffset >> PRECISIONB) * img.width; for (int x = 0; x < destW; x++) { destPixels[destOffset + x] = blend_hard_light(destPixels[destOffset + x], srcBuffer[sY + (sX >> PRECISIONB)]); sX += dx; } destOffset += screenW; srcYOffset += dy; } break; case SOFT_LIGHT: for (int y = 0; y < destH; y++) { sX = srcXOffset; sY = (srcYOffset >> PRECISIONB) * img.width; for (int x = 0; x < destW; x++) { destPixels[destOffset + x] = blend_soft_light(destPixels[destOffset + x], srcBuffer[sY + (sX >> PRECISIONB)]); sX += dx; } destOffset += screenW; srcYOffset += dy; } break; // davbol 2006-12-16 --> } } } ///////////////////////////////////////////////////////////// // BLEND MODE IMPLEMENTIONS // <-- davbol 2006-12-16 renamed old blend_multiply() to blend_blend() private int blend_blend(int a, int b) { // davbol 2006-12-16 --> int f = (b & ALPHA_MASK) >>> 24; return (low(((a & ALPHA_MASK) >>> 24) + f, 0xff) << 24 | mix(a & RED_MASK, b & RED_MASK, f) & RED_MASK | mix(a & GREEN_MASK, b & GREEN_MASK, f) & GREEN_MASK | mix(a & BLUE_MASK, b & BLUE_MASK, f)); } // <-- davbol 2006-12-16 /** * returns the absolute value of the difference of the input colors * C = |A - B| */ private int blend_difference(int a, int b) { // setup (this portion will always be the same) int f = (b & ALPHA_MASK) >>> 24; int ar = (a & RED_MASK) >> 16; int ag = (a & GREEN_MASK) >> 8; int ab = (a & BLUE_MASK); int br = (b & RED_MASK) >> 16; int bg = (b & GREEN_MASK) >> 8; int bb = (b & BLUE_MASK); // formula: int cr = (ar > br) ? (ar-br) : (br-ar); int cg = (ag > bg) ? (ag-bg) : (bg-ag); int cb = (ab > bb) ? (ab-bb) : (bb-ab); // alpha blend (this portion will always be the same) return (low(((a & ALPHA_MASK) >>> 24) + f, 0xff) << 24 | (mix(a, cr, f) << 16) & RED_MASK | (mix(a, cg, f) << 8) & GREEN_MASK | (mix(a, cb, f)) & BLUE_MASK ); } /** * returns the product of the input colors * C = A * B */ private int blend_multiply(int a, int b) { // setup (this portion will always be the same) int f = (b & ALPHA_MASK) >>> 24; int ar = (a & RED_MASK) >> 16; int ag = (a & GREEN_MASK) >> 8; int ab = (a & BLUE_MASK); int br = (b & RED_MASK) >> 16; int bg = (b & GREEN_MASK) >> 8; int bb = (b & BLUE_MASK); // formula: int cr = (ar * br) >> 8; int cg = (ag * bg) >> 8; int cb = (ab * bb) >> 8; // alpha blend (this portion will always be the same) return (low(((a & ALPHA_MASK) >>> 24) + f, 0xff) << 24 | (mix(a, cr, f) << 16) & RED_MASK | (mix(a, cg, f) << 8) & GREEN_MASK | (mix(a, cb, f)) & BLUE_MASK ); } /** * returns the inverse of the product of the inverses of the input colors /* (the inverse of multiply) * C = 1 - (1-A) * (1-B) */ private int blend_screen(int a, int b) { // setup (this portion will always be the same) int f = (b & ALPHA_MASK) >>> 24; int ar = (a & RED_MASK) >> 16; int ag = (a & GREEN_MASK) >> 8; int ab = (a & BLUE_MASK); int br = (b & RED_MASK) >> 16; int bg = (b & GREEN_MASK) >> 8; int bb = (b & BLUE_MASK); // formula: int cr = 255 - (((255 - r1) * (255 - r2)) >> 8); int cg = 255 - (((255 - g1) * (255 - g2)) >> 8); int cb = 255 - (((255 - b1) * (255 - b2)) >> 8); // alpha blend (this portion will always be the same) return (low(((a & ALPHA_MASK) >>> 24) + f, 0xff) << 24 | (mix(a, cr, f) << 16) & RED_MASK | (mix(a, cg, f) << 8) & GREEN_MASK | (mix(a, cb, f)) & BLUE_MASK ); } /** * returns either multiply or screen for darker or lighter values of A * (the inverse of hard light) * C = * A < 0.5 : 2 * A * B * A >=0.5 : 1 - (2 * (255-A) * (255-B)) */ private int blend_overlay(int a, int b) { // setup (this portion will always be the same) int f = (b & ALPHA_MASK) >>> 24; int ar = (a & RED_MASK) >> 16; int ag = (a & GREEN_MASK) >> 8; int ab = (a & BLUE_MASK); int br = (b & RED_MASK) >> 16; int bg = (b & GREEN_MASK) >> 8; int bb = (b & BLUE_MASK); // formula: int cr = (ar < 128) ? ((ar*br)>>7) : (255-(((255-ar)*(255-br))>>7)); int cg = (ag < 128) ? ((ag*bg)>>7) : (255-(((255-ag)*(255-bg))>>7)); int cb = (ab < 128) ? ((ab*bb)>>7) : (255-(((255-ab)*(255-bb))>>7)); // alpha blend (this portion will always be the same) return (low(((a & ALPHA_MASK) >>> 24) + f, 0xff) << 24 | (mix(a, cr, f) << 16) & RED_MASK | (mix(a, cg, f) << 8) & GREEN_MASK | (mix(a, cb, f)) & BLUE_MASK ); } /** * returns either multiply or screen for darker or lighter values of B * (the inverse of overlay) * C = * A < 0.5 : 2 * A * B * A >=0.5 : 1 - (2 * (255-A) * (255-B)) */ private int blend_hard_light(int a, int b) { // setup (this portion will always be the same) int f = (b & ALPHA_MASK) >>> 24; int ar = (a & RED_MASK) >> 16; int ag = (a & GREEN_MASK) >> 8; int ab = (a & BLUE_MASK); int br = (b & RED_MASK) >> 16; int bg = (b & GREEN_MASK) >> 8; int bb = (b & BLUE_MASK); // formula: int cr = (br < 128) ? ((ar*br)>>7) : (255-(((255-ar)*(255-br))>>7)); int cg = (bg < 128) ? ((ag*bg)>>7) : (255-(((255-ag)*(255-bg))>>7)); int cb = (bb < 128) ? ((ab*bb)>>7) : (255-(((255-ab)*(255-bb))>>7)); // alpha blend (this portion will always be the same) return (low(((a & ALPHA_MASK) >>> 24) + f, 0xff) << 24 | (mix(a, cr, f) << 16) & RED_MASK | (mix(a, cg, f) << 8) & GREEN_MASK | (mix(a, cb, f)) & BLUE_MASK ); } /** * returns soft light * C = something that works pretty much like soft light, but faster, * until such time as someone can confirm the REAL formula used by ps */ private int blend_soft_light(int a, int b) { // setup (this portion will always be the same) int f = (b & ALPHA_MASK) >>> 24; int ar = (a & RED_MASK) >> 16; int ag = (a & GREEN_MASK) >> 8; int ab = (a & BLUE_MASK); int br = (b & RED_MASK) >> 16; int bg = (b & GREEN_MASK) >> 8; int bb = (b & BLUE_MASK); // formula: int temp = (ar * br) >> 8; int cr = temp + ar * (255 - ((255-ar) * (255-br) >> 8) - temp) >> 8; temp = (ag * bg) >> 8; int cg = temp + ag * (255 - ((255-ag) * (255-bg) >> 8) - temp) >> 8; temp = (ab * bb) >> 8; int cb = temp + ab * (255 - ((255-ab) * (255-bb) >> 8) - temp) >> 8; // alpha blend (this portion will always be the same) return (low(((a & ALPHA_MASK) >>> 24) + f, 0xff) << 24 | (mix(a, cr, f) << 16) & RED_MASK | (mix(a, cg, f) << 8) & GREEN_MASK | (mix(a, cb, f)) & BLUE_MASK ); } // davbol 2006-12-16 --> }