Files
2025-09-29 00:52:08 +02:00

679 lines
18 KiB
Plaintext
Executable File

/* 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<lHMax; i++)
{
xBuffer[i] = 0;
yBuffer[i] = 0;
}
//stick something in the drawBuffer
drawBuff.beginDraw();
drawBuff.background(127, 127, 127, 0);
drawBuff.noStroke();
//drawBuff.imageMode(CENTER);
drawBuff.image(flowMap, 0, 0);
drawBuff.endDraw();
present = drawBuff.get(0, 0, bmpX, bmpY);
undoBuff = present.get(0, 0, bmpX, bmpY);
blendBrush = flowBrush.get(0, 0, brushSize, brushSize);
}
public void createControlGroup()
{
//controlP5.begin(bmpX+10,10);
controlP5.ControlGroup grpUI = controlP5.addGroup("Controls", bmpX, 0, uiWidth);
grpUI.setBackgroundColor(color(64, 64, 64, 0));
grpUI.hideBar();
controlP5.Slider sldBrushSize = controlP5.addSlider("BrushSize", 1, 50);
sldBrushSize.setPosition( 15, 15 );
sldBrushSize.setNumberOfTickMarks(10);
sldBrushSize.showTickMarks(true);
sldBrushSize.snapToTickMarks(false);
sldBrushSize.moveTo(grpUI);
controlP5.Slider sldFlowSpeed = controlP5.addSlider("FlowSpeed", 1, 255);
sldFlowSpeed.setPosition( 15, 45 );
sldFlowSpeed.setNumberOfTickMarks(16);
sldFlowSpeed.showTickMarks(true);
sldFlowSpeed.snapToTickMarks(false);
sldFlowSpeed.moveTo(grpUI);
controlP5.Slider sldSpeedScale = controlP5.addSlider("SpeedScale", -30, 30);
sldSpeedScale.setValue(speedScale);
sldSpeedScale.setPosition(15, 75);
//sldSpeedScale.setNumberOfTickMarks(10);
//sldSpeedScale.showTickMarks(true);
sldSpeedScale.moveTo(grpUI);
//controlP5.addButton("Smear").linebreak();
controlP5.Toggle tglPaintSmear = controlP5.addToggle("PaintSmear",false, 15, 100, 60, 20);
tglPaintSmear.setMode(ControlP5.SWITCH);
tglPaintSmear.moveTo(grpUI);
controlP5.Toggle tglFE = controlP5.addToggle("FlowEdging",false, 15, 140, 60, 20);
tglFE.setMode(ControlP5.SWITCH);
tglFE.setLabel("Flow / Edging");
tglFE.moveTo(grpUI);
//flowDir
//controlP5.Knob flowDir = controlP5.addKnob("FlowDir", -360, 360, 180, 150, 160, 60);
//flowDir.moveTo(grpUI);
//Buttons
//
controlP5.Bang bngDetail = controlP5.addBang("GenDetailMap", 15, 200, 100, 30);
bngDetail.setLabel("Generate Detail Map");
bngDetail.moveTo(grpUI);
controlP5.Bang bngUndo = controlP5.addBang("Undo", 15, 300, 60, 30);
bngUndo.moveTo(grpUI);
controlP5.Bang bngLoad = controlP5.addBang("Load", 15, 350, 60, 30);
bngLoad.moveTo(grpUI);
controlP5.Bang bngSave = controlP5.addBang("Save", 15, 400, 60, 30);
bngSave.moveTo(grpUI);
controlP5.Bang bngQuit = controlP5.addBang("Quit", 15, 450, 60, 30);
bngQuit.moveTo(grpUI);
//controlP5.end();
}
void draw()
{
// Displays the image at its actual size at point (0,0)
image(present, 0, 0);
}
public void getImageRegion()
{
int brushOffset = floor(brushSize/2);
//int mx = constrain(mouseX, brushOffset, drawBuff.width - brushOffset);
int mx = mouseX - brushOffset;
//int my = constrain(mouseY, brushOffset, drawBuff.height - brushOffset);
int my = mouseY - brushOffset;
if(mx > 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();
}