/* Simple vector painter */ //OpenGL import //import processing.opengl.*; //ControlP5 import controlP5.*; ControlP5 controlP5; int uiWidth = 256; int uiHeight = 512; //Draw Buffer public PGraphics drawBuff; public PImage present; public PImage undoBuff; public PImage region; public PImage blendBrush; PImage flowMap; // Declare variable "a" of type PImage PImage flowDetail; PImage flowBrush; //flowmap path String flowMapPath = ""; //mouse stuff float oldMX; float oldMY; int bs = 20; boolean bover = false; boolean locked = false; float bdifx = 0.0; float bdify = 0.0; //last normalVec float old_nVecX, old_nVecY; PVector v2 = new PVector(mouseX, mouseY); PVector v4 = new PVector(pmouseX, pmouseY); //FlowMap int bmpX = 511; int bmpY = 511; //painting params int brushSize = 32; int flowSpeed = 255; int speedScale = 0; //line history store previous mouse positions int lHMax = 5; //float lineHistory[][] = new float[lHMax][lHMax]; ArrayList mPositions = new ArrayList(); ArrayList leadingEdgeHistory = new ArrayList(); color lastColour = color(127, 127, 127, 0); float xBuffer[] = new float[lHMax]; float yBuffer[] = new float[lHMax]; //Painting modes //paintSmear 1==paint 0==smear boolean paintSmear = true; //Draw flow = 1 edges = 0 boolean flowEdge = false; boolean undo = true; int updateCounter = lHMax; float normColour = 0.003921568627451; int zoom = 1; ////////////////////////////////////////////////////// //FUNCTIONS////////////////////////////////////////// ///////////////////////////////////////////////////// void setup() { size(bmpX+256, bmpY); //frameRate(200); background(64); smooth(); //Controls controlP5 = new ControlP5(this); // begin a new group of auto-arranged controllers createControlGroup(); //drawBuffer drawBuff = createGraphics(bmpX+1, bmpY+1, P3D); flowMap = loadImage("X:/gta5/art/Textures/water/CS3_05_WaterMesh02FLOW.bmp"); // Load the image into the program flowDetail = loadImage("c:/temp/flowDetail2.tga"); flowBrush = requestImage("C:/Users/neil.gregory/Documents/Processing/flowPainter/flowBrush.tga"); //init line history for(int i=0; i 480) { mx = 480; } if(my > 480) { my = 480; } //region = drawBuff.get(mx-brushOffset, my-brushOffset, brushSize, brushSize); region = drawBuff.get(mx, my, brushSize, brushSize); } public void regionBlend() { int brushOffset = (brushSize/2); //region.blend(flowBrush, 0, 0, brushSize, brushSize, 0, 0, brushSize, brushSize, OVERLAY); //get flow brush and resize blendBrush = flowBrush.get(0, 0, flowBrush.width-1, flowBrush.height-1); blendBrush.resize(brushSize, 0); //load pixel arrays region.loadPixels(); blendBrush.loadPixels(); //blend colours for( int i=0; i < region.pixels.length; i++ ) { //println(hex(region.pixels[i])); //return; //region.pixels[i] = min(region.pixels[i] + blendBrush.pixels[i], 255); color srcCol = region.pixels[i]; float srcColR = srcCol >> 16 & 0xFF; float srcColG = srcCol >> 8 & 0xFF; float srcColB = srcCol & 0xFF; srcColB = constrain(1.0 - (1.0 / srcColB), 0.0, 1.0); float vecX = -1.0 + 2.0 * (srcColR / 255.0); float vecY = -1.0 + 2.0 * (srcColG / 255.0); //float srcColB = srcCol & 0xFF; //float srcColA = alpha(srcCol); color dstCol = blendBrush.pixels[i]; float dstColA = constrain(1.0 - (1.0 / alpha(dstCol)), 0.0, 1.0); if( dstColA > 0.0) { //println(dstColA); //println(srcColR); //scale by brush //color newCol = color(dstColA * srcColR, dstColA * srcColG, dstColA * srcColB); //color newCol = color(srcColR*dstColA, srcColG*dstColA, srcColB*dstColA); //float newColR = newCol >> 16 & 0xFF; //float newColG = newCol >> 8 & 0xFF; //float newColB = newCol & 0xFF; //add to colour //float addCol = constrain(5.0 * dstColA, 0.0, 5.0); float addColR = (vecX * speedScale)*dstColA; float addColG = (vecY * speedScale)*dstColA; //println(addColR); //println(addColG); //println(addCol); //color newCol = color((srcColR*dstColA)+addCol, (srcColG*dstColA)+addCol, 127); //color newCol = color(); //check limits //if( srcColR < 255 && srcColG < 255 ) //{ color newCol = color(srcColR+addColR, srcColG+addColG, 127); region.pixels[i] = newCol; //} } } region.updatePixels(); //blendBrush.updatePixels(); //blendBrush.blend(region, 0, 0, brushSize, brushSize, 0, 0, brushSize, brushSize, OVERLAY); //image(region, 0, 0); //region.blend(flowBrush, 0, 0, flowBrush.width, flowBrush.height, 0, 0, brushSize, brushSize, ADD); //region.blend(flowBrush, brushOffset, brushOffset, brushSize, brushSize, 0, 0, brushSize, brushSize, ADD); //region.blend(flowBrush, brushOffset, brushOffset, brushSize, brushSize, 0, 0, brushSize, brushSize, LIGHTEST); //region.blend(region, 0, 0, brushSize, brushSize, 0, 0, brushSize, brushSize, MULTIPLY); //region.blend(region, 0, 0, brushSize, brushSize, 0, 0, brushSize, brushSize, ADD); } void mousePressed() { //Cursor change cursor(CROSS); //clear the mouse positions ArrayList mPositions.clear(); //Store the current image in the undo buffer undoBuff = drawBuff.get(0, 0, bmpX, bmpY); //undoBuff = flowMap.get(0, 0, flowMap.width, flowMap.height); //undoBuff.loadPixels(); //undoBuff.updatePixels(); } void mouseDragged() { //undoBuff = drawBuff.get(0, 0, drawBuff.width, drawBuff.height); //mouse history average float mAvgX = 0.0; float mAvgY = 0.0; for(int i=0; i < mPositions.size(); i++) { float mPos[] = (float[])mPositions.get(i); mAvgX += mPos[0]; mAvgY += mPos[1]; } mAvgX = mAvgX / (float)lHMax; mAvgY = mAvgY / (float)lHMax; //what kinda paintin' we doin' // //grab under the mouse getImageRegion(); //Now blend with the brush regionBlend(); //stuff back in the image //drawBuff.imageMode(CENTER); int brushOffset = brushSize/2; drawBuff.set(mouseX-brushOffset, mouseY-brushOffset, region); //drawBuff.blend(flowBrush, mouseX, mouseY, brushSize, brushSize, drawBuff.width, drawBuff.height, 10, 10, DIFFERENCE); //drawBuff.tint(255, 0, 0); //region.blend(drawBuff, 0, 0, brushSize, brushSize, mouseX - (brushSize/2), mouseY - (brushSize/2), region.width, region.height, BLEND); //drawBuff.image(region, mouseX - floor(brushSize/2), mouseY - floor(brushSize/2), brushSize, brushSize); //drawBuff.image(region, mouseX, mouseY, brushSize, brushSize); //drawBuff.set(region, mouseX, mouseY); if( updateCounter == lHMax ) { //reset counter updateCounter = 0; /* PVector mouseVec = new PVector(mouseX, mouseY); PVector pmouseVec = new PVector(oldMX, oldMY); PVector flowVec = PVector.sub(mouseVec, pmouseVec); //normalize flowVec.normalize(); //normal vec to flowVec PVector flowNormal = new PVector( flowVec.y, -flowVec.x ); //PVector flowNormalNeg = PVector.mult(flowNormal, -1); PVector flowNormalNeg = new PVector(-flowVec.x, -flowVec.y); //scale by brushSize flowNormal.mult(brushSize); flowNormalNeg.mult(brushSize); //vertex positions // // v5------v0------v1 // | | | // v4-------v3-----v2 // PVector v1 = new PVector( mouseX + flowNormal.x, mouseY + flowNormal.y); //PVector v2 = new PVector( oldMX + flowNormal.x, oldMY + flowNormal.y); //PVector v4 = new PVector( oldMX - flowNormal.x, oldMY - flowNormal.y); PVector v5 = new PVector( mouseX - flowNormal.x, mouseY - flowNormal.y); //colour scaling factor //float vScale = 0.1 * vecMag; float r = 127 + (127*flowVec.x); float g = 127 + (127*flowVec.y); float b = 127; if (flowEdge == true) //Draw flows { drawBuff.beginDraw(); drawBuff.pushMatrix(); /* drawBuff.beginShape(LINES); drawBuff.stroke(255, 255, 255, 255); drawBuff.vertex(v1.x, v1.y, 0.0); drawBuff.vertex(v5.x, v5.y, 0.0); drawBuff.endShape(); drawBuff.beginShape(QUADS); //quad1 drawBuff.fill(r, g, b, 255); drawBuff.vertex(mouseX, mouseY, 0.0); drawBuff.fill(lastColour); drawBuff.vertex(oldMX, oldMY, 0.0); drawBuff.fill(127, 127, 127, 0); drawBuff.vertex( v2.x, v2.y, 0.0); drawBuff.vertex( v1.x, v1.y, 0.0); //quad2 drawBuff.fill(r, g, b, 255); drawBuff.vertex(mouseX, mouseY, 0.0); drawBuff.fill(lastColour); drawBuff.vertex(oldMX, oldMY, 0.0); drawBuff.fill(127, 127, 127, 0); drawBuff.vertex( v4.x, v4.y, 0.0); drawBuff.vertex( v5.x, v5.y, 0.0); drawBuff.endShape(); drawBuff.popMatrix(); drawBuff.endDraw(); } else if(flowEdge == false) //Draw edges { drawBuff.beginDraw(); drawBuff.pushMatrix(); drawBuff.beginShape(QUADS); drawBuff.fill(r, g, b, 255); drawBuff.vertex(mouseX, mouseY, 0.0); drawBuff.fill(lastColour); drawBuff.vertex(oldMX, oldMY, 0.0); drawBuff.fill(127, 127, 127, 0); drawBuff.vertex( v2.x, v2.y, 0.0); drawBuff.vertex( v1.x, v1.y, 0.0); drawBuff.endShape(); drawBuff.popMatrix(); drawBuff.endDraw(); } //present the buffer //present = drawBuff.get(0, 0, drawBuff.width, drawBuff.height); //present.blend(drawBuff, 0, 0, drawBuff.width, drawBuff.height, 0, 0, present.width, present.height, BLEND); //present.blend(flowMap, 0, 0, flowMap.width, flowMap.height, 0, 0, present.width, present.height, OVERLAY); //copy new normal vec to old v2 = v1; v4 = v5; //Add mouseXY to mPositions float mPos[] = new float[2]; mPos[0] = mouseX; mPos[1] = mouseY; mPositions.add(mPos); //trim to size if( mPositions.size() > lHMax ) { mPositions.remove(0); } */ //update lastColour with the current one //lastColour = color(r, g, b, 255); } //increment counter updateCounter += 1; //Add leading edges corner vert positions //float v1Pos[] = new float[2]; //v1Pos[0] = //bmpX = (int)(bmpX * (1.0 / zoom)); //bmpY = (int)(bmpY * (1.0 / zoom)); //present.blend(drawBuff, 0, 0, drawBuff.width, drawBuff.height, 0, 0, present.width, present.height, BLEND); present = drawBuff.get(0, 0, bmpX, bmpY); //present = region.get(0, 0, brushSize, brushSize); } void mouseReleased() { //println(mPositions.size()); //println(mPositions.get(1)); //Cursor change cursor(ARROW); } ////////////////////////////// //Key pressed functions ////////////////////////////// void keyPressed() { if( key == 'c') //clear screen { background(127); //image(flowMap, 0, 0); } if( key == '.') //increase brush size { brushSize += 1; strokeWeight(brushSize); } if( key == ',') //decrease brush size { brushSize -= 1; if( brushSize <= 1 ) { brushSize = 1; } strokeWeight(brushSize); } if( key == 'u') //undo hotkey { drawBuff.set(0, 0, undoBuff); present = undoBuff.get(0, 0, bmpX, bmpY); image(present, 0, 0); } if( key == 'z' ) //Zoom in { zoom += 1; println(zoom); } if( key == 'x' ) //Zoom out { zoom -= 1; } } ///////////////////// //ControlP5 events //////////////////// public void BrushSize(int v) { brushSize = v; } public void FlowSpeed(int v) { flowSpeed = v; } public void SpeedScale(int v) { speedScale = v; } public void FlowEdging(boolean mode) { if (mode == true) { flowEdge = true; } else if (mode == false) { flowEdge = false; } } //Generate a detail flow map from the flowDetail and flowmap public void GenDetailMap() { //create a new PImage //PImage detailMap = createImage(flowDetail.width, flowDetail.height, RGB); //Process the pixels from flowDetail flowDetail.loadPixels(); flowMap.loadPixels(); //detailMap.loadPixels(); int width = flowDetail.width; for(int x=0; x < flowDetail.width; x++) { for(int y=0; y < flowDetail.height; y++) { //y = abs(y - flowDetail.height); //get colour from detail map as a vector color detailCol = flowDetail.pixels[(width-y-1)*width+x]; //y-flip //check for non null colour to process otherwise move on if( detailCol != -8355712 ) { //println(detailCol); float detailCol_R = detailCol >> 16 & 0xFF; float detailCol_G = detailCol >> 8 & 0xFF; float detailCol_B = detailCol & 0xFF; //Create vec, making sure to remap colour range into cartesian space (-127) PVector detailVec = new PVector(detailCol_R - 127, detailCol_G - 127, 0); detailVec.normalize(); //println(detailVec); //get flowmap colour color flowMapCol = flowMap.pixels[y*width+x]; float flowMapCol_R = flowMapCol >> 16 & 0xFF; float flowMapCol_G = flowMapCol >> 8 & 0xFF; float flowMapCol_B = flowMapCol & 0xFF; //Create vec, making sure to remap colour range into cartesian space (-127) PVector flowMapVec = new PVector(flowMapCol_R - 127, flowMapCol_G - 127, 0); flowMapVec.normalize(); //dot of two colour vecs float flowProduct = PVector.dot(detailVec, flowMapVec); //println(flowProduct); //flowProduct = flowProduct - 1.0; //println(flowProduct); PVector newFlowVec = PVector.sub(flowMapVec, detailVec); newFlowVec.normalize(); //0-1 -> 0-255 newFlowVec.mult(255); //Now blend based on the dot, -ve values opposing flow are strong //+ve values are weaken flow //0 value has no effect //float diffR = flowMapCol_R + (127 - detailCol_R); //looks promising //float diffG = flowMapCol_G - (127 - detailCol_G); float diffR = constrain(flowMapCol_R + (0.5*(newFlowVec.x * flowProduct)), 0, 255); float diffG = constrain(flowMapCol_G + (0.5*(newFlowVec.y * flowProduct)), 0, 255); //if(flowProduct //color newFlow = color(diffR * flowProduct, diffG * flowProduct, flowMapCol_B); //color newFlow = color(flowMapCol_R, flowMapCol_G, 127.0); //color newFlow = color(flowMapCol_R, flowMapCol_G, 127.0); //color newFlow = color(detailCol_R, detailCol_G, 127.0); //color result = lerpColor(flowMapCol, newFlow, flowProduct); //color newFlow = color(newFlowVec.x * flowProduct, newFlowVec.y * flowProduct, flowMapCol_B); color newFlow = color(diffR, diffG, flowMapCol_B); color result = newFlow; flowMap.pixels[y*width+x] = result; } } } //update flowmap flowMap.updatePixels(); //show it present = flowMap.get(0, 0, flowMap.width, flowMap.height); } //Load a flowmap to work on public void Load() { String flowMapPath = selectInput("Select FlowMap"); if( flowMapPath != null ) { println(flowMapPath); flowMap = loadImage(flowMapPath); drawBuff.set(0, 0, flowMap); //image(flowMap, 0, 0); } return; } //Save a modified flowmap public void Save() { String outputPath = selectOutput("Select Save Path"); if( outputPath != null ) { println(outputPath); flowMap.save(outputPath); } //bngDetail.setValue(false); } //Undo Event public void Undo() { present = undoBuff.get(0, 0, bmpX, bmpY); drawBuff.set(0, 0, undoBuff); image(undoBuff, 0, 0); } //Quit Event public void Quit() { //Check for save changes //Exit exit(); }