*/}}
YimingWu преди 8 месеца
родител
ревизия
ae1fc8889e
променени са 3 файла, в които са добавени 173 реда и са изтрити 23 реда
  1. 70 15
      ouroperations.c
  2. 17 1
      ourpaint.h
  3. 86 7
      ourshader.cpp

+ 70 - 15
ouroperations.c

@@ -126,17 +126,30 @@ void ourui_LayersPanel(laUiList *uil, laPropPack *This, laPropPack *DetachedProp
     }laEndCondition(uil,b);
 
     laShowSeparator(uil,c);
-    b=laBeginRow(uil,c,0,0);
-    lui=laShowLabel(uil,c,"Background:",0,0);lui->Expand=1;lui->Flags|=LA_TEXT_ALIGN_RIGHT;
-        laUiItem* b2=laOnConditionThat(uil,c,laPropExpression(0,"our.lock_background"));{
-            laShowItemFull(uil,c,0,"our.lock_background",LA_WIDGET_ENUM_CYCLE,0,0,0)->Flags|=LA_UI_FLAGS_EXIT_WHEN_TRIGGERED;
-        }laElse(uil,b2);{
-            laShowItemFull(uil,c,0,"our.canvas.background_color",LA_WIDGET_FLOAT_COLOR,0,0,0);
-        }laEndCondition(uil,b2);
-    laEndRow(uil,b);
+
     b=laBeginRow(uil,c,0,0);
     lui=laShowLabel(uil,c,"Color Space:",0,0);lui->Expand=1;lui->Flags|=LA_TEXT_ALIGN_RIGHT; laShowItem(uil,c,0,"our.canvas.color_interpretation");
     laEndRow(uil,b);
+
+    laShowSeparator(uil,c);
+
+    laShowLabel(uil,c,"Background:",0,0);
+    laUiItem* b2=laOnConditionThat(uil,c,laPropExpression(0,"our.lock_background"));{
+        laShowItemFull(uil,c,0,"our.lock_background",LA_WIDGET_ENUM_CYCLE,0,0,0)->Flags|=LA_UI_FLAGS_EXIT_WHEN_TRIGGERED;
+    }laElse(uil,b2);{
+        b=laBeginRow(uil,c,1,0);
+        laShowLabel(uil,c,"Color:",0,0);
+        laShowItemFull(uil,c,0,"our.canvas.background_color",LA_WIDGET_FLOAT_COLOR,0,0,0);
+        laEndRow(uil,b);
+        b=laBeginRow(uil,c,1,0);
+        laShowLabel(uil,c,"Pattern:",0,0);
+        laShowItemFull(uil,c,0,"our.canvas.background_type",0,0,0,0)->Flags|=LA_UI_FLAGS_EXPAND;
+        laEndRow(uil,b);
+        b=laBeginRow(uil,c,1,0);
+        laShowItemFull(uil,c,0,"our.canvas.background_random",0,0,0,0);
+        laShowItemFull(uil,c,0,"our.canvas.background_factor",0,0,0,0);
+        laEndRow(uil,b);
+    }laEndCondition(uil,b2);
 }
 void ourui_Brush(laUiList *uil, laPropPack *This, laPropPack *DetachedProps, laColumn *UNUSED, int context){
     laColumn* c=laFirstColumn(uil); laColumn* cl,*cr; laSplitColumn(uil,c,0.7); cl=laLeftColumn(c,0);cr=laRightColumn(c,1);
@@ -180,16 +193,22 @@ void ourui_ToolsPanel(laUiList *uil, laPropPack *This, laPropPack *DetachedProps
             OUR_BR laShowItem(uil,c,0,"our.tools.current_brush.hardness")->Expand=1;  OUR_PRESSURE("pressure_hardness") OUR_ER
             laShowItem(uil,c,0,"our.tools.current_brush.slender");
             laShowItem(uil,c,0,"our.tools.current_brush.angle");
-            OUR_BR laShowItem(uil,c,0,"our.tools.current_brush.smudge")->Expand=1;  OUR_PRESSURE("pressure_smudge")  OUR_ER
             laShowItem(uil,c,0,"our.tools.current_brush.dabs_per_size");
+            OUR_BR laShowItem(uil,c,0,"our.tools.current_brush.smudge")->Expand=1;  OUR_PRESSURE("pressure_smudge")  OUR_ER
             laShowItem(uil,c,0,"our.tools.current_brush.smudge_resample_length");
+            laShowItem(uil,c,0,"our.tools.current_brush.gunkyness");
+            OUR_BR laShowItem(uil,c,0,"our.tools.current_brush.force")->Expand=1;
+            laShowItemFull(uil,c,0,"our.tools.current_brush.pressure_force",0,"text=P",0,0); OUR_ER
+            laShowSeparator(uil,c);
             laShowItem(uil,c,0,"our.tools.current_brush.smoothness");
+            laShowSeparator(uil,c);
             b2=laOnConditionThat(uil,c,laPropExpression(0,"our.tools.current_brush.use_nodes"));
                 laShowItem(uil,cl,0,"our.tools.current_brush.c1");
                 laShowItem(uil,cr,0,"our.tools.current_brush.c1_name");
                 laShowItem(uil,cl,0,"our.tools.current_brush.c2");
                 laShowItem(uil,cr,0,"our.tools.current_brush.c2_name");
             laEndCondition(uil,b2);
+            laShowSeparator(uil,c);
             laShowItem(uil,c,0,"our.tools.current_brush.default_as_eraser");
         }laEndCondition(uil,b);
 
@@ -643,6 +662,7 @@ OurBrush* our_NewBrush(char* name, real Size, real Hardness, real DabsPerSize, r
     memAssignRef(Our, &Our->CurrentBrush, b);
     b->Rack=memAcquire(sizeof(laRackPage)); b->Rack->RackType=LA_RACK_TYPE_DRIVER;
     b->Binding=-1;
+    b->PressureForce=1; b->Force=1;
     return b;
 }
 void our_RemoveBrush(OurBrush* b){
@@ -1190,6 +1210,7 @@ void our_UiToCanvas(laCanvasExtra* ex, laEvent*e, real* x, real *y){
 }
 void our_PaintResetBrushState(OurBrush* b){
     b->BrushRemainingDist = 0; b->SmudgeAccum=0; b->SmudgeRestart=1;
+    Our->LastBrushCenter[0]=-1e21;
 }
 real our_PaintGetDabStepDistance(real Size,real DabsPerSize){
     real d=Size/DabsPerSize; if(d<1e-2) d=1e-2; return d;
@@ -1209,7 +1230,7 @@ int our_PaintGetDabs(OurBrush* b, OurLayer* l, real x, real y, real xto, real yt
     real xmin=FLT_MAX,xmax=-FLT_MAX,ymin=FLT_MAX,ymax=-FLT_MAX;
     b->EvalSize=b->Size; b->EvalHardness=b->Hardness; b->EvalSmudge=b->Smudge; b->EvalSmudgeLength=b->SmudgeResampleLength;
     b->EvalTransparency=b->Transparency; b->EvalDabsPerSize=b->DabsPerSize; b->EvalSlender=b->Slender; b->EvalAngle=b->Angle;
-    b->EvalSpeed=tnsDistIdv2(x,y,xto,yto)/b->Size;
+    b->EvalSpeed=tnsDistIdv2(x,y,xto,yto)/b->Size; b->EvalForce=b->Force; b->EvalGunkyness=b->Gunkyness;
     if(Our->ResetBrush){ b->LastX=x; b->LastY=y; b->LastAngle=atan2(yto-y,xto-x); b->EvalStrokeLength=0; Our->ResetBrush=0; }
     real this_angle=atan2(yto-y,xto-x);
     if(b->LastAngle-this_angle>TNS_PI){ this_angle+=(TNS_PI*2); }
@@ -1217,7 +1238,7 @@ int our_PaintGetDabs(OurBrush* b, OurLayer* l, real x, real y, real xto, real yt
 
     while(1){ int Repeat=1; OurDab* od;
         for(b->Iteration=0;b->Iteration<Repeat;b->Iteration++){ b->EvalDiscard=0;
-            arrEnsureLength(&Our->Dabs,Our->NextDab,&Our->MaxDab,sizeof(OurDab)); od=&Our->Dabs[Our->NextDab];
+            arrEnsureLength(&Our->Dabs,Our->NextDab,&Our->MaxDab,sizeof(OurDab)); od=&Our->Dabs[Our->NextDab]; od->Direction[0]=-1e21;
             real r=tnsGetRatiod(0,len,uselen-rem); od->X=tnsInterpolate(x,xto,r); od->Y=tnsInterpolate(y,yto,r); TNS_CLAMP(r,0,1);
             b->LastX=od->X; b->LastY=od->Y; tnsVectorSet3v(b->EvalColor, Our->CurrentColor);
             if(b->UseNodes){
@@ -1234,6 +1255,8 @@ int our_PaintGetDabs(OurBrush* b, OurLayer* l, real x, real y, real xto, real yt
             od->Smudge = b->EvalSmudge*pfac(b->PressureSmudge); od->Color[3]=pow(b->EvalTransparency*pfac(b->PressureTransparency),2.718);
             tnsVectorSet3v(od->Color,b->EvalColor);
     #undef pfac;
+            od->Force=b->EvalForce*(b->PressureForce?tnsInterpolate(last_pressure,pressure,r):1);
+            od->Gunkyness = b->EvalGunkyness;
             od->Slender = b->EvalSlender; od->Angle=b->EvalAngle;
             xmin=TNS_MIN2(xmin, od->X-od->Size); xmax=TNS_MAX2(xmax, od->X+od->Size); 
             ymin=TNS_MIN2(ymin, od->Y-od->Size); ymax=TNS_MAX2(ymax, od->Y+od->Size);
@@ -1269,9 +1292,13 @@ void our_PaintDoSample(int x, int y, int sx, int sy, int ssize, int last,int beg
 void our_PaintDoDab(OurDab* d, int tl, int tr, int tu, int tb){
     int corner[2]; corner[0]=floorf(d->X-d->Size); corner[1]=floorf(d->Y-d->Size);
     real MaxX,MaxY; MaxX=ceil(d->X+d->Size); MaxY=ceil(d->Y+d->Size);
+    float center[2]; center[0]=d->X-tl; center[1]=d->Y-tu;
+    if(d->Direction[0]<-1e20){
+        if(Our->LastBrushCenter[0]<-1e20){ d->Direction[0]=0;d->Direction[1]=0; }
+        else{ d->Direction[0]=d->X-Our->LastBrushCenter[0]; d->Direction[1]=d->Y-Our->LastBrushCenter[1]; }
+    } tnsVectorSet2(Our->LastBrushCenter,d->X,d->Y);
     if(corner[0]>tr||MaxX<tl||corner[1]>tb||MaxY<tu) return;
     corner[0]=corner[0]-tl; corner[1]=corner[1]-tu;
-    float center[2]; center[0]=d->X-tl; center[1]=d->Y-tu;
     glUniform2iv(Our->uBrushCorner,1,corner);
     glUniform2fv(Our->uBrushCenter,1,center);
     glUniform1f(Our->uBrushSize,d->Size);
@@ -1279,6 +1306,9 @@ void our_PaintDoDab(OurDab* d, int tl, int tr, int tu, int tb){
     glUniform1f(Our->uBrushSmudge,d->Smudge);
     glUniform1f(Our->uBrushSlender,d->Slender);
     glUniform1f(Our->uBrushAngle,d->Angle);
+    glUniform2fv(Our->uBrushDirection,1,d->Direction);
+    glUniform1f(Our->uBrushForce,d->Force);
+    glUniform1f(Our->uBrushGunkyness,d->Gunkyness);
     glUniform1f(Our->uBrushRecentness,d->Recentness);
     glUniform4fv(Our->uBrushColor,1,d->Color);
     glDispatchCompute(ceil(d->Size/16), ceil(d->Size/16), 1);
@@ -1289,9 +1319,10 @@ void our_PaintDoDabs(OurLayer* l,int tl, int tr, int tu, int tb, int Start, int
         for(int col=tl;col<=tr;col++){
             OurTexTile* ott=l->TexTiles[row][col];
             glBindImageTexture(0, ott->Texture->GLTexHandle, 0, GL_FALSE, 0, GL_READ_WRITE, GL_RGBA16);
-            int sx=l->TexTiles[row][col]->l,sy=l->TexTiles[row][col]->b;
+            int s[2]; s[0]=l->TexTiles[row][col]->l,s[1]=l->TexTiles[row][col]->b;
+            glUniform2iv(Our->uImageOffset,1,s);
             for(int i=Start;i<End;i++){
-                our_PaintDoDab(&Our->Dabs[i],sx,sx+OUR_TILE_W,sy,sy+OUR_TILE_W);
+                our_PaintDoDab(&Our->Dabs[i],s[0],s[0]+OUR_TILE_W,s[1],s[1]+OUR_TILE_W);
             }
         }
     }
@@ -1317,7 +1348,9 @@ void our_PaintDoDabsWithSmudgeSegments(OurLayer* l,int tl, int tr, int tu, int t
     uniforms[Our->uBrushRoutineSelection]=Our->RoutineDoDabs;
     uniforms[Our->uMixRoutineSelection]=Our->SpectralMode?Our->RoutineDoMixSpectral:Our->RoutineDoMixNormal;
     glUniformSubroutinesuiv(GL_COMPUTE_SHADER,2,uniforms);
-    
+    glUniform1i(Our->uCanvasType,Our->BackgroundType);
+    glUniform1i(Our->uCanvasRandom,Our->BackgroundRandom);
+    glUniform1f(Our->uCanvasFactor,Our->BackgroundFactor);
 
     while(oss=lstPopItem(&Segments)){
         if(oss->Resample || Our->CurrentBrush->SmudgeRestart){
@@ -2059,6 +2092,8 @@ void ourRegisterEverything(){
     laAddFloatProperty(pc,"slender","Slender","Slenderness of the brush",0,0, 0,10,0,0.1,0,0,offsetof(OurBrush,Slender),0,0,0,0,0,0,0,0,0,0,0);
     laAddFloatProperty(pc,"angle","Angle","Angle of the brush",0,0, 0,TNS_PI,-TNS_PI,0.1,0,0,offsetof(OurBrush,Angle),0,0,0,0,0,0,0,0,0,0,LA_RAD_ANGLE);
     laAddFloatProperty(pc,"smoothness","Smoothness","Smoothness of the brush",0,0, 0,1,0,0.05,0,0,offsetof(OurBrush,Smoothness),0,0,0,0,0,0,0,0,0,0,0);
+    laAddFloatProperty(pc,"force","Force","How hard the brush is pushed against canvas texture",0,0,0,1,0,0.05,0,0,offsetof(OurBrush,Force),0,0,0,0,0,0,0,0,0,0,0);
+    laAddFloatProperty(pc,"gunkyness","Gunkyness","How will the brush stick to the canvas texture",0,0, 0,1,-1,0.05,0,0,offsetof(OurBrush,Gunkyness),0,0,0,0,0,0,0,0,0,0,0);
     laAddFloatProperty(pc,"c1","C1","Custom brush input 1",0,0, 0,0,0,0.05,0,0,offsetof(OurBrush,Custom1),0,0,0,0,0,0,0,0,0,0,0);
     laAddFloatProperty(pc,"c2","C2","Custom brush input 2",0,0, 0,0,0,0.05,0,0,offsetof(OurBrush,Custom2),0,0,0,0,0,0,0,0,0,0,0);
     laAddStringProperty(pc,"c1_name","C1 Name","Custom input 1 name",0,0,0,0,1,offsetof(OurBrush,Custom1Name),0,0,0,0,0);
@@ -2071,6 +2106,8 @@ void ourRegisterEverything(){
     OUR_ADD_PRESSURE_SWITCH(p);
     p=laAddEnumProperty(pc,"pressure_smudge","Pressure Smudge","Use pen pressure to control smudging",LA_WIDGET_ENUM_HIGHLIGHT,0,0,0,0,offsetof(OurBrush,PressureSmudge),0,0,0,0,0,0,0,0,0,0);
     OUR_ADD_PRESSURE_SWITCH(p);
+    p=laAddEnumProperty(pc,"pressure_force","Pressure Force","Use pen pressure to control dab force",LA_WIDGET_ENUM_HIGHLIGHT,0,0,0,0,offsetof(OurBrush,PressureForce),0,0,0,0,0,0,0,0,0,0);
+    OUR_ADD_PRESSURE_SWITCH(p);
     p=laAddEnumProperty(pc,"use_nodes","Use Nodes","Use nodes to control brush dynamics",LA_WIDGET_ENUM_HIGHLIGHT,0,0,0,0,offsetof(OurBrush,UseNodes),0,0,0,0,0,0,0,0,0,0);
     laAddEnumItemAs(p,"NONE","None","Not using nodes",0,0);
     laAddEnumItemAs(p,"ENABLED","Enabled","Using nodes",1,0);
@@ -2092,6 +2129,12 @@ void ourRegisterEverything(){
     laAddIntProperty(pc,"size","Size","Size of the cropping area",0,"W,H","px",0,0,0,2400,0,offsetof(OurPaint,W),0,0,2,0,0,0,0,ourset_CanvasSize,0,0,0);
     laAddIntProperty(pc,"position","Position","Position of the cropping area",0,"X,Y","px",0,0,0,2400,0,offsetof(OurPaint,X),0,0,2,0,0,0,0,ourset_CanvasPosition,0,0,0);
     laAddFloatProperty(pc,"background_color","Background Color","Background color of the canvas",0,"R,G,B",0,1,0,0.05,0.8,0,offsetof(OurPaint,BackgroundColor),0,0,3,0,0,0,0,ourset_BackgroundColor,0,0,LA_PROP_IS_LINEAR_SRGB);
+    p=laAddEnumProperty(pc,"background_type","Background Type","Background texture type",0,0,0,0,0,offsetof(OurPaint,BackgroundType),0,0,0,0,0,0,0,0,0,0);
+    laAddEnumItemAs(p,"NONE","None","No textured background",0,0);
+    laAddEnumItemAs(p,"CANVAS","Canvas","Background mimics canvas texture",1,0);
+    laAddEnumItemAs(p,"PAPER","Paper","Background mimics paper texture",2,0);
+    laAddIntProperty(pc,"background_random","Random","Background random pattern value",0,0,0,0,0,0,0,0,offsetof(OurPaint,BackgroundRandom),0,0,0,0,0,0,0,0,0,0,0);
+    laAddFloatProperty(pc,"background_factor","Factor","Background effect factor",0,0,0,1,0,0,0,0,offsetof(OurPaint,BackgroundFactor),0,0,0,0,0,0,0,0,0,0,0);
     laAddFloatProperty(pc,"border_alpha","Border Alpha","Alpha of the border region around the canvas",0,0,0,1,0,0.05,0.5,0,offsetof(OurPaint,BorderAlpha),0,0,0,0,0,0,0,ourset_BorderAlpha,0,0,0);
     p=laAddEnumProperty(pc,"show_border","Show Border","Whether to show border on the canvas",0,0,0,0,0,offsetof(OurPaint,ShowBorder),0,ourset_ShowBorder,0,0,0,0,0,0,0,0);
     laAddEnumItemAs(p,"FALSE","No","Dont' show border on the canvas",0,0);
@@ -2232,6 +2275,10 @@ int ourInit(){
         glGetProgramInfoLog(Our->CompositionProgram, sizeof(error), 0, error); printf("Composition program Linking error:\n%s", error); return 0;
     }
 
+    Our->uCanvasType=glGetUniformLocation(Our->CanvasProgram,"uCanvasType");
+    Our->uCanvasRandom=glGetUniformLocation(Our->CanvasProgram,"uCanvasRandom");
+    Our->uCanvasFactor=glGetUniformLocation(Our->CanvasProgram,"uCanvasFactor");
+    Our->uImageOffset=glGetUniformLocation(Our->CanvasProgram,"uImageOffset");
     Our->uBrushCorner=glGetUniformLocation(Our->CanvasProgram,"uBrushCorner");
     Our->uBrushCenter=glGetUniformLocation(Our->CanvasProgram,"uBrushCenter");
     Our->uBrushSize=glGetUniformLocation(Our->CanvasProgram,"uBrushSize");
@@ -2241,6 +2288,9 @@ int ourInit(){
     Our->uBrushColor=glGetUniformLocation(Our->CanvasProgram,"uBrushColor");
     Our->uBrushSlender=glGetUniformLocation(Our->CanvasProgram,"uBrushSlender");
     Our->uBrushAngle=glGetUniformLocation(Our->CanvasProgram,"uBrushAngle");
+    Our->uBrushDirection=glGetUniformLocation(Our->CanvasProgram,"uBrushDirection");
+    Our->uBrushForce=glGetUniformLocation(Our->CanvasProgram,"uBrushForce");
+    Our->uBrushGunkyness=glGetUniformLocation(Our->CanvasProgram,"uBrushGunkyness");
     Our->uBrushErasing=glGetUniformLocation(Our->CanvasProgram,"uBrushErasing");
 
     Our->uBrushRoutineSelection=glGetSubroutineUniformLocation(Our->CanvasProgram, GL_COMPUTE_SHADER, "uBrushRoutineSelection");
@@ -2263,6 +2313,11 @@ int ourInit(){
 
     Our->SpectralMode=1;
 
+    Our->BackgroundType=2;
+    Our->BackgroundFactor=1;
+    srand(time(0));
+    Our->BackgroundRandom=rand()-RAND_MAX/2;
+
     Our->LockRadius=1;
     Our->EnableBrushCircle=1;
     Our->PaintUndoLimit=100;

+ 17 - 1
ourpaint.h

@@ -159,11 +159,12 @@ STRUCTURE(OurBrush){
     real SmudgeResampleLength; real SmudgeAccum; int SmudgeRestart; real BrushRemainingDist;
     real Slender;
     real Angle;
+    real Force, Gunkyness;
     real Smoothness;
     real MaxStrokeLength;
     real Custom1,Custom2; laSafeString *Custom1Name,*Custom2Name;
     int Iteration;
-    int PressureSize,PressureHardness,PressureTransparency,PressureSmudge; // the simple way
+    int PressureSize,PressureHardness,PressureTransparency,PressureSmudge,PressureForce; // the simple way
 
     int Binding,DefaultAsEraser;
 
@@ -182,6 +183,7 @@ STRUCTURE(OurBrush){
     real EvalSmudgeLength;
     real EvalSlender;
     real EvalAngle;
+    real EvalForce, EvalGunkyness;
 
     real EvalSpeed;
     real EvalStrokeLength;
@@ -203,6 +205,9 @@ STRUCTURE(OurDab){
     float Color[4];
     float Slender;
     float Angle;
+    float Direction[2];
+    float Force;
+    float Gunkyness;
     float Recentness;
 };
 
@@ -274,6 +279,7 @@ STRUCTURE(OurPaint){
     OurBrush*    CurrentBrush;
     real SaveBrushSize,SaveEraserSize;
     OurDab* Dabs; int NextDab,MaxDab;
+    float LastBrushCenter[2];
 
     real Smoothness;
     real LastX, LastY;
@@ -283,6 +289,9 @@ STRUCTURE(OurPaint){
 
     int Tool,ActiveTool,Erasing,EventErasing;
     int LockBackground;
+    int BackgroundType;
+    int BackgroundRandom;
+    real BackgroundFactor;
     int PenID,EraserID;
     int X,Y,W,H; //border
     int ColorInterpretation;
@@ -301,6 +310,10 @@ STRUCTURE(OurPaint){
     tnsTexture* SmudgeTexture;
     GLuint CanvasShader;      GLuint CanvasProgram;
     GLuint CompositionShader; GLuint CompositionProgram;
+    GLint uCanvasType;
+    GLint uCanvasRandom;
+    GLint uCanvasFactor;
+    GLint uImageOffset;
     GLint uBrushCorner;
     GLint uBrushCenter;
     GLint uBrushSize;
@@ -310,6 +323,9 @@ STRUCTURE(OurPaint){
     GLint uBrushColor;
     GLint uBrushSlender;
     GLint uBrushAngle;
+    GLint uBrushDirection;
+    GLint uBrushForce;
+    GLint uBrushGunkyness;
     GLint uBrushRoutineSelection;
     GLint uMixRoutineSelection;
     GLint uBrushErasing;

+ 86 - 7
ourshader.cpp

@@ -22,6 +22,10 @@ const char OUR_CANVAS_SHADER[]=R"(#version 430
 layout(local_size_x = 32, local_size_y = 32, local_size_z = 1) in;
 layout(rgba16, binding = 0) uniform image2D img;
 layout(rgba16, binding = 1) coherent uniform image2D smudge_buckets;
+uniform int uCanvasType;
+uniform int uCanvasRandom;
+uniform float uCanvasFactor;
+uniform ivec2 uImageOffset;
 uniform ivec2 uBrushCorner;
 uniform vec2 uBrushCenter;
 uniform float uBrushSize;
@@ -29,6 +33,9 @@ uniform float uBrushHardness;
 uniform float uBrushSmudge;
 uniform float uBrushSlender;
 uniform float uBrushAngle;
+uniform vec2 uBrushDirection;
+uniform float uBrushForce;
+uniform float uBrushGunkyness;
 uniform float uBrushRecentness;
 uniform vec4 uBrushColor;
 uniform vec4 uBackgroundColor;
@@ -77,6 +84,75 @@ vec3 spectral_to_rgb (float spectral[10]) {
     for (int i=0; i<3; i++) {rgb_[i] = clamp((tmp[i] - WGM_EPSILON) / offset, 0.0f, 1.0f);}
     return rgb_;
 }
+
+vec2 hash( vec2 p ){
+	p = vec2( dot(p,vec2(127.1,311.7)), dot(p,vec2(269.5,183.3)) );
+	return -1.0 + 2.0*fract(sin(p)*43758.5453123);
+}
+float rand(vec2 co){
+    return fract(sin(dot(co, vec2(12.9898, 78.233))) * 43758.5453);
+}
+float noise(in vec2 p){ // from iq
+    const float K1 = 0.366025404; // (sqrt(3)-1)/2;
+    const float K2 = 0.211324865; // (3-sqrt(3))/6;
+	vec2  i = floor( p + (p.x+p.y)*K1 );
+    vec2  a = p - i + (i.x+i.y)*K2;
+    float m = step(a.y,a.x); 
+    vec2  o = vec2(m,1.0-m);
+    vec2  b = a - o + K2;
+	vec2  c = a - 1.0 + 2.0*K2;
+    vec3  h = max( 0.5-vec3(dot(a,a), dot(b,b), dot(c,c) ), 0.0 );
+	vec3  n = h*h*h*h*vec3( dot(a,hash(i+0.0)), dot(b,hash(i+o)), dot(c,hash(i+1.0)));
+    return dot( n, vec3(70.0) );
+}
+
+#define HEIGHT_STRAND(x,y) abs(fract(x)-.5)<.48? \
+    (.4+.2*sin(3.14*(y+ceil(x))))* \
+        ((max(abs(sin(3.14*x*2.)+0.2),abs(sin(3.14*x*2.)-0.2))+2.*abs(sin(3.14*x)))/2.+0.5):0.1
+#define PATTERN_CANVAS(x,y) \
+    (max(HEIGHT_STRAND((x),(y)),HEIGHT_STRAND(-(y),(x))))
+
+float HEIGHT_CANVAS(float x,float y){
+    if(uCanvasType == 1){
+        return PATTERN_CANVAS(x,y);
+    }else if(uCanvasType == 2){
+        vec2 uv=vec2(x,y); float f; uv*=0.1; // from iq
+        mat2 m = mat2(1.6,1.2,-1.2,1.6);
+        f  = 0.1*noise( uv ); uv = m*uv;
+        f += 0.1*noise( uv ); uv = m*uv;
+        f += 0.2*noise( uv ); uv = m*uv;
+        f += 0.3*noise( uv ); uv = m*uv;
+		f += 0.5*noise( uv ); uv = m*uv;
+		f += 0.4*noise( uv ); uv = m*uv;
+	    f = 0.5 + 0.5*f;
+        return f;
+    }
+    return 1;
+}
+float SampleCanvas(vec2 U, vec2 dir,float rfac, float force, float gunky){
+    if(uCanvasType==0 || abs(gunky-0.)<1e-2){ return rfac; }
+    U+=vec2(uImageOffset); U/=20.3; U.x=U.x+rand(U)/10.; U.y=U.y+rand(U)/10.;
+
+    mat2 m = mat2(1.6,1.2,-1.2,1.6); vec2 _uv=U; _uv.x+=float(uCanvasRandom%65535)/174.41; _uv.y+=float(uCanvasRandom%65535)/439.87; _uv/=500.;
+    U.x+=noise(_uv)*2.1; _uv = m*_uv; U.x+=noise(_uv)*0.71;
+    _uv.y+=365.404;
+    U.y+=noise(_uv)*1.9; _uv = m*_uv; U.y+=noise(_uv)*0.83;
+    
+    float d=0.1;
+    float h=HEIGHT_CANVAS(U.x,U.y);
+    float hr=HEIGHT_CANVAS(U.x+d,U.y);
+    float hu=HEIGHT_CANVAS(U.x,U.y+d);
+    vec3 vx=normalize(vec3(d,0,hr)-vec3(0,0,h)),vy=normalize(vec3(0,d,hu)-vec3(0,0,h)),vz=cross(vx,vy);
+    float useforce=force*rfac;
+    float scrape=dot(normalize(vz),vec3(-normalize(dir).xy,0))*mix(0.3,1.,useforce);
+    float top=h-(1.-pow(useforce,1.5)*2);
+    float fac=(gunky>=0.)?mix(1.,top,gunky):mix(1.,1.-h,-gunky*0.8);
+    fac=max(fac,scrape*clamp(gunky,0,1));
+    fac=clamp(fac,0,1);
+    fac*=rfac;
+    return mix(rfac,fac,uCanvasFactor);
+}
+
 subroutine vec4 MixRoutines(vec4 a, vec4 b, float fac_a);
 subroutine(MixRoutines) vec4 DoMixNormal(vec4 a, vec4 b, float fac_a){
     return mix(a,b,1-fac_a);
@@ -123,18 +199,21 @@ vec4 mix_over(vec4 colora, vec4 colorb){
     m=vec4(m.rgb*m.a,m.a);
     return m;
 }
-int dab(float d, vec4 color, float size, float hardness, float smudge, vec4 smudge_color, vec4 last_color, out vec4 final){
+int dab(float d, vec2 fpx, vec4 color, float size, float hardness, float smudge, vec4 smudge_color, vec4 last_color, out vec4 final){
     vec4 cc=color;
     float fac=1-pow(d/size,1+1/(1-hardness+1e-4));
-    cc.a=color.a*fac*(1-smudge); cc.rgb=cc.rgb*cc.a;
+    float canvas=SampleCanvas(fpx,uBrushDirection,fac,uBrushForce,uBrushGunkyness);
+    cc.a=color.a*canvas*(1-smudge); cc.rgb=cc.rgb*cc.a;
     float erasing=float(uBrushErasing);
     cc=cc*(1-erasing);
+
     // this looks better than the one commented out below
-    vec4 c2=spectral_mix_unpre(last_color,smudge_color,smudge*fac*color.a);
+    vec4 c2=spectral_mix_unpre(last_color,smudge_color,smudge*fac*color.a*canvas);
     c2=mix_over(cc,c2);
     //vec4 c2=mix_over(cc,last_color);
-    //c2=spectral_mix_unpre(c2,smudge_color,smudge*fac*color.a);
-    c2=spectral_mix_unpre(c2,c2*(1-fac*color.a),erasing);
+    //c2=spectral_mix_unpre(c2,smudge_color,smudge*fac*color.a*canvas);
+
+    c2=spectral_mix_unpre(c2,c2*(1-fac*color.a),erasing*canvas);
     final=c2;
     return 1;
 }
@@ -142,14 +221,14 @@ subroutine void BrushRoutines();
 subroutine(BrushRoutines) void DoDabs(){
     ivec2 px = ivec2(gl_GlobalInvocationID.xy)+uBrushCorner;
     if(px.x<0||px.y<0||px.x>1024||px.y>1024) return;
-    vec2 fpx=vec2(px);
+    vec2 fpx=vec2(px),origfpx=fpx;
     fpx=uBrushCenter+rotate(fpx-uBrushCenter,uBrushAngle);
     fpx.x=uBrushCenter.x+(fpx.x-uBrushCenter.x)*(1+uBrushSlender);
     float dd=distance(fpx,uBrushCenter); if(dd>uBrushSize) return;
     vec4 dabc=imageLoad(img, px);
     vec4 smudgec=pow(spectral_mix_unpre(pow(imageLoad(smudge_buckets,ivec2(1,0)),p1_22),pow(imageLoad(smudge_buckets,ivec2(0,0)),p1_22),uBrushRecentness),p22);
     vec4 final_color;
-    dab(dd,uBrushColor,uBrushSize,uBrushHardness,uBrushSmudge,smudgec,dabc,final_color);
+    dab(dd,origfpx,uBrushColor,uBrushSize,uBrushHardness,uBrushSmudge,smudgec,dabc,final_color);
     dabc=final_color;
     imageStore(img, px, dabc);
 }