*/}}
Browse Source

Merge branch 'master' of http://www.wellobserve.com/repositories/chengdulittlea/OurPaint

YimingWu 1 month ago
parent
commit
60cfc00a6e
3 changed files with 315 additions and 24 deletions
  1. BIN
      default_brushes.udf
  2. 295 23
      ouroperations.c
  3. 20 1
      ourpaint.h

BIN
default_brushes.udf


+ 295 - 23
ouroperations.c

@@ -89,10 +89,19 @@ void ourui_CanvasPanel(laUiList *uil, laPropPack *This, laPropPack *DetachedProp
     laUiItem* ui=laShowCanvas(uil,c,0,"our.canvas",0,-1);
     laCanvasExtra* ce=ui->Extra; ce->ZoomX=ce->ZoomY=1.0f/Our->DefaultScale;
 }
+void ourui_ThumbnailPanel(laUiList *uil, laPropPack *This, laPropPack *DetachedProps, laColumn *UNUSED, int context){
+    laColumn* c=laFirstColumn(uil);
+    laUiItem* ui=laShowCanvas(uil,c,0,"our.canvas",0,-1);
+    laCanvasExtra* ce=ui->Extra; ce->ZoomX=ce->ZoomY=1.0f/Our->DefaultScale;
+    ce->SelectThrough = 1;
+}
 void ourui_Layer(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);
     laUiItem* b=laBeginRow(uil,cl,0,0);
     laShowHeightAdjuster(uil,cl,This,"__move",0);
+    laUiItem* b0=laOnConditionThat(uil,cr,laPropExpression(This,"as_sketch"));{
+        laShowLabel(uil,cl,"🖉",0,0);
+    }laEndCondition(uil,b0);
     laShowItemFull(uil,cl,This,"name",LA_WIDGET_STRING_PLAIN,0,0,0)->Expand=1;
     laShowItemFull(uil,cl,This,"lock",LA_WIDGET_ENUM_CYCLE_ICON,0,0,0)->Flags|=LA_UI_FLAGS_NO_DECAL;
     laShowItemFull(uil,cl,This,"hide",LA_WIDGET_ENUM_CYCLE_ICON,0,0,0)->Flags|=LA_UI_FLAGS_NO_DECAL;
@@ -101,6 +110,8 @@ void ourui_Layer(laUiList *uil, laPropPack *This, laPropPack *DetachedProps, laC
         b=laBeginRow(uil,c,0,0);
         laShowItem(uil,c,This,"remove")->Flags|=LA_UI_FLAGS_ICON;
         laShowSeparator(uil,c)->Expand=1;
+        laShowItem(uil,c,This,"as_sketch")->Flags|=LA_UI_FLAGS_EXPAND|LA_UI_FLAGS_ICON;
+        laShowSeparator(uil,c);
         laShowItemFull(uil,c,This,"move",0,"direction=up;icon=🡱;",0,0)->Flags|=LA_UI_FLAGS_ICON;
         laShowItemFull(uil,c,This,"move",0,"direction=down;icon=🡳;",0,0)->Flags|=LA_UI_FLAGS_ICON;
         laEndRow(uil,b);
@@ -127,11 +138,19 @@ void ourui_LayersPanel(laUiList *uil, laPropPack *This, laPropPack *DetachedProp
         laShowItem(uil,c,&lui->PP,"remove")->Flags|=LA_UI_FLAGS_ICON;
         laShowItem(uil,c,&lui->PP,"merge");
         laShowSeparator(uil,c)->Expand=1;
+        laShowItem(uil,c,&lui->PP,"duplicate");
         laEndRow(uil,b1);
     }laEndCondition(uil,b);
 
     laShowSeparator(uil,c);
 
+    b=laBeginRow(uil,c,0,0);
+    laShowItem(uil,c,0,"OUR_cycle_sketch")->Expand=1;
+    laShowSeparator(uil,c); laShowItem(uil,c,0,"our.canvas.sketch_mode");
+    laEndRow(uil,b);
+
+    laShowSeparator(uil,c);
+
     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);
@@ -263,6 +282,10 @@ void ourui_ToolsPanel(laUiList *uil, laPropPack *This, laPropPack *DetachedProps
             laShowLabel(uil,cl,"Position:",0,0); laShowItem(uil,cl,0,"our.canvas.position")->Flags|=LA_UI_FLAGS_TRANSPOSE;
             laShowLabel(uil,cl,"Size:",0,0); laShowItem(uil,cl,0,"our.canvas.size")->Flags|=LA_UI_FLAGS_TRANSPOSE;
             laShowItem(uil,cl,0,"OUR_crop_to_ref");
+            laUiItem* b1=laBeginRow(uil,cl,1,0);
+            laShowItemFull(uil,cl,0,"OUR_crop_to_ref",0,"border=inner;text=Inner",0,0);
+            laShowItemFull(uil,cl,0,"OUR_crop_to_ref",0,"border=outer;text=Outer",0,0);
+            laEndRow(uil,b1);
         }laEndCondition(uil,b);
         
         laShowLabel(uil,cr,"Reference:",0,0);
@@ -467,7 +490,12 @@ void our_EnableSplashPanel(){
 void our_CanvasDrawTextures(){
     tnsUseImmShader; tnsEnableShaderv(T->immShader); real MultiplyColor[4];
     for(OurLayer* l=Our->Layers.pLast;l;l=l->Item.pPrev){
-        if(l->Hide || l->Transparency==1) continue; real a=1-l->Transparency; tnsVectorSet4(MultiplyColor,a,a,a,a); int any=0; 
+        if(l->Hide || l->Transparency==1) continue; real a=1-l->Transparency;
+        if(Our->SketchMode && l->AsSketch){
+            if(Our->SketchMode == 1){ a=1.0f; }
+            elif(Our->SketchMode == 2){ a=0.0f; }
+        }
+        tnsVectorSet4(MultiplyColor,a,a,a,a); int any=0; 
         if(l->BlendMode==OUR_BLEND_NORMAL){ glBlendFunc(GL_ONE,GL_ONE_MINUS_SRC_ALPHA); }
         if(l->BlendMode==OUR_BLEND_ADD){ glBlendFunc(GL_ONE,GL_ONE); }
         for(int row=0;row<OUR_TILES_PER_ROW;row++){
@@ -563,8 +591,34 @@ void our_CanvasDrawReferenceBlock(OurCanvasDraw* ocd){
     tnsFlush();
 }
 void our_CanvasDrawBrushCircle(OurCanvasDraw* ocd){
-    if(!Our->CurrentBrush) return; real v[96]; real Radius=Our->CurrentBrush->Size/ocd->Base.ZoomX,gap=rad(2);
+    real colorw[4]={1,1,1,0.3}; real colork[4]={0,0,0,0.3};
+    if(Our->Tool==OUR_TOOL_MOVE || (Our->Tool==OUR_TOOL_CROP && Our->ShowBorder)){
+        tnsUseImmShader();
+        tnsDrawStringM("🤚",0,colork,ocd->Base.OnX-LA_RH,ocd->Base.OnX+10000,ocd->Base.OnY-LA_RH,0);
+        tnsDrawStringM("🤚",0,colorw,ocd->Base.OnX-2-LA_RH,ocd->Base.OnX+10000,ocd->Base.OnY-2-LA_RH,0);
+        return;
+    }
+    real v[96]; real Radius=(Our->CurrentBrush?Our->CurrentBrush->Size:100.0f)/ocd->Base.ZoomX, gap=rad(2);
     tnsUseImmShader();tnsUseNoTexture(); tnsLineWidth(1.5);
+    OurLayer* l = Our->CurrentLayer;
+    if (!Our->CurrentBrush || !l || l->Hide || l->Transparency==1 || l->Lock ||
+        (l->AsSketch && Our->SketchMode==2)|| ocd->Base.SelectThrough || (Our->Tool==OUR_TOOL_CROP && !Our->ShowBorder)){
+        real d = Radius * 0.707;
+        tnsColor4d(0,0,0,0.3);
+        tnsVertex2d(ocd->Base.OnX-d+1, ocd->Base.OnY+d-1); tnsVertex2d(ocd->Base.OnX+d+1, ocd->Base.OnY-d-1);
+        tnsVertex2d(ocd->Base.OnX-d+1, ocd->Base.OnY-d-1); tnsVertex2d(ocd->Base.OnX+d+1, ocd->Base.OnY+d-1);
+        tnsPackAs(GL_LINES);
+        tnsColor4d(1,1,1,0.3);
+        tnsVertex2d(ocd->Base.OnX-d, ocd->Base.OnY+d-1); tnsVertex2d(ocd->Base.OnX+d, ocd->Base.OnY-d-1);
+        tnsVertex2d(ocd->Base.OnX-d, ocd->Base.OnY-d-1); tnsVertex2d(ocd->Base.OnX+d, ocd->Base.OnY+d-1);
+        tnsPackAs(GL_LINES);
+        return;
+    }
+    if(Our->ShowBrushName){
+        tnsDrawStringAuto(SSTR(Our->CurrentBrush->Name),colork,ocd->Base.OnX-10000,ocd->Base.OnX-LA_RH,ocd->Base.OnY-LA_RH,LA_TEXT_ALIGN_RIGHT);
+        tnsDrawStringAuto(SSTR(Our->CurrentBrush->Name),colorw,ocd->Base.OnX-10000,ocd->Base.OnX-LA_RH-2,ocd->Base.OnY-2-LA_RH,LA_TEXT_ALIGN_RIGHT);
+        tnsUseNoTexture();
+    }
     tnsMakeCircle2d(v,48,ocd->Base.OnX,ocd->Base.OnY,Radius+0.5,0);
     tnsColor4d(1,1,1,0.3); tnsVertexArray2d(v,48); tnsPackAs(GL_LINE_LOOP);
     tnsMakeCircle2d(v,48,ocd->Base.OnX,ocd->Base.OnY,Radius-0.5,0);
@@ -704,12 +758,30 @@ void our_GetBrushOffset(OurCanvasDraw* ocd_if_scale, OurBrush*b, real event_orie
 
 int ourextramod_Canvas(laOperator *a, laEvent *e){
     laUiItem *ui = a->Instance; OurCanvasDraw* ocd=ui->Extra;
+    if(ocd->Base.SelectThrough && e->type==LA_L_MOUSE_DOWN) return LA_RUNNING;
     if(Our->EnableBrushCircle && ((e->type&LA_MOUSE_EVENT)||(e->type&LA_KEYBOARD_EVENT))){
         ocd->PointerX = e->x; ocd->PointerY = e->y; real offx,offy;
         our_GetBrushOffset(0,Our->CurrentBrush,e->Orientation,&offx,&offy);
         ocd->Base.OnX=e->x-offx; ocd->Base.OnY=e->y-offy;
         laRedrawCurrentPanel(); Our->EventHasTwist=e->HasTwist; Our->EventTwistAngle=e->Twist;
     }
+
+    if(e->type==LA_SIGNAL_EVENT){
+        switch(e->key){
+        case OUR_SIGNAL_BRUSH_SMALLER: 
+            laInvoke(a,"OUR_brush_resize",e,0,"direction=smaller",0);
+            break;
+        case OUR_SIGNAL_BRUSH_BIGGER:
+            laInvoke(a,"OUR_brush_resize",e,0,"direction=bigger",0);
+            break;
+        case OUR_SIGNAL_ZOOM_IN: 
+            laInvoke(a,"LA_2d_view_zoom",e,&ui->ExtraPP,"direction=in",0);
+            break;
+        case OUR_SIGNAL_ZOOM_OUT:
+            laInvoke(a,"LA_2d_view_zoom",e,&ui->ExtraPP,"direction=out",0);
+            break;
+        }
+    }
     return LA_RUNNING_PASS;
 }
 
@@ -718,6 +790,28 @@ OurLayer* our_NewLayer(char* name){
     memAssignRef(Our, &Our->CurrentLayer, l);
     return l;
 }
+void our_DuplicateLayerContent(OurLayer* to, OurLayer* from){
+    for(int row=0;row<OUR_TILES_PER_ROW;row++){ if(!from->TexTiles[row]) continue;
+        to->TexTiles[row]=memAcquire(sizeof(OurTexTile*)*OUR_TILES_PER_ROW);
+        for(int col=0;col<OUR_TILES_PER_ROW;col++){ if(!from->TexTiles[row][col] || !from->TexTiles[row][col]->Texture) continue;
+            to->TexTiles[row][col]=memAcquire(sizeof(OurTexTile));
+            OurTexTile* tt=to->TexTiles[row][col],*ft=from->TexTiles[row][col];
+            memcpy(tt,ft,sizeof(OurTexTile));
+            tt->CopyBuffer=0;
+            tt->Texture=tnsCreate2DTexture(GL_RGBA16,OUR_TILE_W,OUR_TILE_W,0);
+            int bufsize=sizeof(uint16_t)*OUR_TILE_W*OUR_TILE_W*4;
+            tt->FullData=malloc(bufsize);
+
+            ft->Data=malloc(bufsize); int width=OUR_TILE_W;
+            tnsBindTexture(ft->Texture); glPixelStorei(GL_PACK_ALIGNMENT, 2);
+            glGetTextureSubImage(ft->Texture->GLTexHandle, 0, 0, 0, 0, width, width,1, GL_RGBA, GL_UNSIGNED_SHORT, bufsize, ft->Data);
+            tnsBindTexture(tt->Texture);
+            glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, width, GL_RGBA, GL_UNSIGNED_SHORT, ft->Data);
+            
+            free(ft->Data); ft->Data=0;
+        }
+    }
+}
 void ourbeforefree_Layer(OurLayer* l){
     for(int row=0;row<OUR_TILES_PER_ROW;row++){ if(!l->TexTiles[row]) continue;
         for(int col=0;col<OUR_TILES_PER_ROW;col++){ if(!l->TexTiles[row][col]) continue;
@@ -1054,27 +1148,36 @@ static void _our_png_write(png_structp png_ptr, png_bytep data, png_size_t lengt
 void our_ImageConvertForExport(int BitDepth, int ColorProfile){
     uint8_t* NewImage;
     cmsHTRANSFORM cmsTransform = NULL;
-    cmsHPROFILE input_buffer_profile = NULL;
-    cmsHPROFILE output_buffer_profile = NULL;
+    cmsHPROFILE input_buffer_profile=NULL,input_gamma_profile=NULL;
+    cmsHPROFILE output_buffer_profile=NULL;
 
     if(BitDepth==OUR_EXPORT_BIT_DEPTH_16){ return; /* only export 16bit flat */ }
 
     input_buffer_profile=(Our->ColorInterpretation==OUR_CANVAS_INTERPRETATION_CLAY)?
         cmsOpenProfileFromMem(Our->icc_LinearClay,Our->iccsize_LinearClay):cmsOpenProfileFromMem(Our->icc_LinearsRGB,Our->iccsize_LinearsRGB);
+    input_gamma_profile=(Our->ColorInterpretation==OUR_CANVAS_INTERPRETATION_CLAY)?
+        cmsOpenProfileFromMem(Our->icc_Clay,Our->icc_Clay):cmsOpenProfileFromMem(Our->icc_sRGB,Our->iccsize_sRGB);
 
+    NewImage=calloc(Our->ImageW*sizeof(uint8_t),Our->ImageH*4);
     if(ColorProfile!=OUR_EXPORT_COLOR_MODE_FLAT){
         if(ColorProfile==OUR_EXPORT_COLOR_MODE_SRGB){ output_buffer_profile=cmsOpenProfileFromMem(Our->icc_sRGB,Our->iccsize_sRGB); }
         elif(ColorProfile==OUR_EXPORT_COLOR_MODE_CLAY){ output_buffer_profile=cmsOpenProfileFromMem(Our->icc_Clay,Our->iccsize_Clay); }
-        cmsTransform = cmsCreateTransform(input_buffer_profile, TYPE_RGBA_16, output_buffer_profile, TYPE_RGBA_16, INTENT_PERCEPTUAL, 0);
-        cmsDoTransform(cmsTransform,Our->ImageBuffer,Our->ImageBuffer,Our->ImageW*Our->ImageH);
-        cmsCloseProfile(input_buffer_profile);cmsCloseProfile(output_buffer_profile); cmsDeleteTransform(cmsTransform);
-    }
-    NewImage=calloc(Our->ImageW*sizeof(uint8_t),Our->ImageH*4);
-    for(int row=0;row<Our->ImageH;row++){
-        for(int col=0;col<Our->ImageW;col++){ uint8_t* p=&NewImage[(row*Our->ImageW+col)*4]; uint16_t* p0=&Our->ImageBuffer[(row*Our->ImageW+col)*4];
-            p[0]=((real)p0[0]+0.5)/256; p[1]=((real)p0[1]+0.5)/256; p[2]=((real)p0[2]+0.5)/256; p[3]=((real)p0[3]+0.5)/256;
+        cmsTransform = cmsCreateTransform(input_buffer_profile, TYPE_RGBA_16, input_gamma_profile, TYPE_RGBA_8,
+            INTENT_ABSOLUTE_COLORIMETRIC, cmsFLAGS_COPY_ALPHA|cmsFLAGS_HIGHRESPRECALC);
+        cmsDoTransform(cmsTransform,Our->ImageBuffer,NewImage,Our->ImageW*Our->ImageH);
+        if(input_gamma_profile!=output_buffer_profile){
+            cmsTransform = cmsCreateTransform(input_gamma_profile, TYPE_RGBA_8, output_buffer_profile, TYPE_RGBA_8,
+                INTENT_ABSOLUTE_COLORIMETRIC, cmsFLAGS_COPY_ALPHA|cmsFLAGS_HIGHRESPRECALC);
+            cmsDoTransform(cmsTransform,NewImage,NewImage,Our->ImageW*Our->ImageH);
+        }
+    }else{
+        for(int row=0;row<Our->ImageH;row++){
+            for(int col=0;col<Our->ImageW;col++){ uint8_t* p=&NewImage[(row*Our->ImageW+col)*4]; uint16_t* p0=&Our->ImageBuffer[(row*Our->ImageW+col)*4];
+                p[0]=((real)p0[0])/256; p[1]=((real)p0[1])/256; p[2]=((real)p0[2])/256; p[3]=((real)p0[3])/256;
+            }
         }
     }
+    cmsCloseProfile(input_buffer_profile);cmsCloseProfile(input_gamma_profile);cmsCloseProfile(output_buffer_profile); cmsDeleteTransform(cmsTransform);
     free(Our->ImageBuffer); Our->ImageBuffer=NewImage;
 }
 int our_ImageExportPNG(FILE* fp, int WriteToBuffer, void** buf, int* sizeof_buf, int UseFrame, int BitDepth, int ColorProfile){
@@ -1527,13 +1630,15 @@ void our_ReadWidgetColor(laCanvasExtra*e,int x,int y){
 }
 
 int our_RenderThumbnail(uint8_t** buf, int* sizeof_buf){
-    int x=INT_MAX,y=INT_MAX,w=-INT_MAX,h=-INT_MAX;
+    int x=INT_MAX,y=INT_MAX,x1=-INT_MAX,y1=-INT_MAX,w=-INT_MAX,h=-INT_MAX;
     for(OurLayer* l=Our->Layers.pFirst;l;l=l->Item.pNext){
         our_LayerClearEmptyTiles(l);
         our_LayerEnsureImageBuffer(l,1);
         if(Our->ImageX<x) x=Our->ImageX; if(Our->ImageY<y) y=Our->ImageY;
-        if(Our->ImageW>w) w=Our->ImageW; if(Our->ImageH>h) h=Our->ImageH;
+        if(Our->ImageW+Our->ImageX>x1) x1=Our->ImageW+Our->ImageX;
+        if(Our->ImageH+Our->ImageY>y1) y1=Our->ImageH+Our->ImageY;
     }
+    w = x1-x; h=y1-y;
     if(w<=0||h<=0) return 0;
     real r = (real)(TNS_MAX2(w,h))/400.0f;
     int use_w=w/r, use_h=h/r;
@@ -1617,6 +1722,76 @@ void our_DoCropping(OurCanvasDraw* cd, real x, real y){
     cd->CanvasLastX+=dx; cd->CanvasLastY+=dy;
 }
 
+void our_LayerGetRange(OurLayer* ol, int* rowmin,int* rowmax, int* colmin, int* colmax){
+    *rowmin = *colmin = INT_MAX; *rowmax = *colmax = -INT_MAX;
+    for(int row=0;row<OUR_TILES_PER_ROW;row++){ if(!ol->TexTiles[row]) continue;
+        if(*rowmin==INT_MAX){ *rowmin = row; }
+        *rowmax=row;
+        for(int col=0;col<OUR_TILES_PER_ROW;col++){ if(!ol->TexTiles[row][col]) continue;
+            if(col > *colmax){ *colmax=col; }
+            if(col < *colmin){ *colmin = col; }
+        }
+    }
+}
+int our_MoveLayer(OurLayer* ol, int dx, int dy, int* movedx, int* movedy){
+    if(!ol || (!dx && !dy)){ *movedx=0; *movedy=0; return 0; }
+    int rowmin,rowmax,colmin,colmax; our_LayerGetRange(ol,&rowmin,&rowmax,&colmin,&colmax);
+
+    if(dx+colmax >= OUR_TILES_PER_ROW){ dx = OUR_TILES_PER_ROW - colmax - 1; }
+    if(dy+rowmax >= OUR_TILES_PER_ROW){ dy = OUR_TILES_PER_ROW - rowmax - 1; }
+    if(colmin + dx < 0){ dx = -colmin; }
+    if(rowmin + dy < 0){ dy = -rowmin; }
+
+    if(!dx && !dy){ *movedx=0; *movedy=0; return 0; }
+
+    if(movedx){ *movedx=dx; }
+    if(movedy){ *movedy=dy; }
+
+    OurTexTile*** copy = memAcquire(sizeof(void*)*OUR_TILES_PER_ROW);
+    for(int i=0;i<OUR_TILES_PER_ROW;i++){ copy[i] = memAcquire(sizeof(void*)*OUR_TILES_PER_ROW); }
+    for(int row=0;row<OUR_TILES_PER_ROW;row++){ if(!ol->TexTiles[row]) continue;
+        for(int col=0;col<OUR_TILES_PER_ROW;col++){
+            copy[row][col] = ol->TexTiles[row][col];
+            ol->TexTiles[row][col] = 0;
+        }
+    }
+    for(int row=rowmin;row<=rowmax;row++){ if(!ol->TexTiles[row]) continue;
+        for(int col=colmin;col<=colmax;col++){
+            OurTexTile* t0=copy[row][col]; if(!t0){continue; }
+            t0->l+=dx*OUR_TILE_W; t0->r+=dx*OUR_TILE_W;
+            t0->u+=dy*OUR_TILE_W; t0->b+=dy*OUR_TILE_W;
+            if(!ol->TexTiles[row+dy]){
+                ol->TexTiles[row+dy] = memAcquire(sizeof(OurTexTile*)*OUR_TILES_PER_ROW);
+            }
+            ol->TexTiles[row+dy][col+dx] = t0;
+        }
+    }
+    for(int i=0;i<OUR_TILES_PER_ROW;i++){ memFree(copy[i]); }
+    memFree(copy);
+    
+    return 1;
+}
+void ourundo_Move(OurMoveUndo* undo){
+    our_LayerClearEmptyTiles(undo->Layer);
+    our_MoveLayer(undo->Layer, -undo->dx, -undo->dy,0,0);
+    laNotifyUsers("our.canvas");
+}
+void ourredo_Move(OurMoveUndo* undo){
+    our_LayerClearEmptyTiles(undo->Layer);
+    our_MoveLayer(undo->Layer, undo->dx, undo->dy,0,0);
+    laNotifyUsers("our.canvas");
+}
+void our_DoMoving(OurCanvasDraw* cd, real x, real y, int *movedx, int *movedy){
+    OurLayer* ol=Our->CurrentLayer; if(!ol){ return; }
+    int dx = x-cd->CanvasDownX, dy = y-cd->CanvasDownY;
+    dx/=OUR_TILE_W_USE; dy/=OUR_TILE_W_USE;
+    if(dx || dy){
+        our_MoveLayer(ol, dx,dy,movedx,movedy);
+        laNotifyUsers("our.canvas");
+        cd->CanvasDownX+=dx*OUR_TILE_W_USE; cd->CanvasDownY+=dy*OUR_TILE_W_USE;
+    }
+}
+
 int ourinv_ShowSplash(laOperator* a, laEvent* e){
     our_EnableSplashPanel();return LA_FINISHED;
 }
@@ -1626,6 +1801,23 @@ int ourinv_NewLayer(laOperator* a, laEvent* e){
     laRecordDifferences(0,"our.canvas.layers");laRecordDifferences(0,"our.canvas.current_layer");laPushDifferences("New Layer",0);
     return LA_FINISHED;
 }
+int ourinv_DuplicateLayer(laOperator* a, laEvent* e){
+    OurLayer* l=a->This?a->This->EndInstance:Our->CurrentLayer;
+    if(!l){ return LA_FINISHED; }
+    our_NewLayer(SSTR(Our->CurrentLayer->Name));
+    our_DuplicateLayerContent(Our->CurrentLayer,l);
+
+    int rowmin,rowmax,colmin,colmax;
+    our_LayerGetRange(Our->CurrentLayer,&rowmin,&rowmax,&colmin,&colmax);
+    int xmin,xmax,ymin,ymax;
+    xmin =((real)colmin-OUR_TILE_CTR-0.5)*OUR_TILE_W_USE; ymin=((real)rowmin-OUR_TILE_CTR-0.5)*OUR_TILE_W_USE;
+    xmax =((real)colmax+1-OUR_TILE_CTR+0.5)*OUR_TILE_W_USE; ymax=((real)rowmax+1-OUR_TILE_CTR-0.5)*OUR_TILE_W_USE;
+    our_RecordUndo(Our->CurrentLayer,xmin,xmax,ymin,ymax,1,0);
+    laRecordDifferences(0,"our.canvas.layers");laRecordDifferences(0,"our.canvas.current_layer");laPushDifferences("New Layer",0);
+    
+    laNotifyUsers("our.canvas"); laMarkMemChanged(Our->CanvasSaverDummyList.pFirst);
+    return LA_FINISHED;
+}
 int ourinv_RemoveLayer(laOperator* a, laEvent* e){
     OurLayer* l=a->This?a->This->EndInstance:0; if(!l) return LA_CANCELED;
     our_RemoveLayer(l,0); laNotifyUsers("our.canvas.layers"); laNotifyUsers("our.canvas"); laMarkMemChanged(Our->CanvasSaverDummyList.pFirst);
@@ -1706,7 +1898,8 @@ int ourmod_ImportLayer(laOperator* a, laEvent* e){
                 FILE* fp=fopen(ex->FilePath->Ptr,"rb"); if(!fp) return LA_FINISHED;
                 if(!ol) ol=our_NewLayer("Imported");
                 int OutMode=ex->OutputMode?ex->OutputMode:((Our->ColorInterpretation==OUR_CANVAS_INTERPRETATION_SRGB)?OUR_PNG_READ_OUTPUT_LINEAR_SRGB:OUR_PNG_READ_OUTPUT_LINEAR_CLAY);
-                our_LayerImportPNG(ol, fp, 0, ex->InputMode, OutMode, 0, 0, 0);
+                int UseOffsets = ex->Offsets[0] && ex->Offsets[1];
+                our_LayerImportPNG(ol, fp, 0, ex->InputMode, OutMode, UseOffsets, ex->Offsets[0], ex->Offsets[1]);
                 laNotifyUsers("our.canvas"); laNotifyUsers("our.canvas.layers"); laMarkMemChanged(Our->CanvasSaverDummyList.pFirst);
                 laRecordDifferences(0,"our.canvas.layers");laRecordDifferences(0,"our.canvas.current_layer");laPushDifferences("New Layer",0);
                 our_LayerRefreshLocal(ol);
@@ -1744,6 +1937,13 @@ void ourui_ImportLayer(laUiList *uil, laPropPack *This, laPropPack *Operator, la
     }laElse(uil,b);{
         laShowLabel(uil,c,"Input image is not tagged as sRGB.",0,0)->Flags|=LA_UI_FLAGS_DISABLED;
     }laEndCondition(uil,b);
+    laUiItem* row = laBeginRow(uil,cl,0,0);
+    laShowLabel(uil,cl,"Use Offsets",0,0);
+    b=laOnConditionToggle(uil,cl,0,0,0,0,0);{
+        laEndRow(uil,row);
+        laShowItem(uil,cl,Operator,"offsets");
+    }laEndCondition(uil,b);
+    laEndRow(uil,row);
 
     b=laBeginRow(uil,c,0,0);laShowSeparator(uil,c)->Expand=1;laShowItem(uil,c,0,"LA_confirm")->Flags|=LA_UI_FLAGS_HIGHLIGHT;laEndRow(uil,b);
 }
@@ -1841,10 +2041,17 @@ int ourinv_MoveBrush(laOperator* a, laEvent* e){
 int ourinv_BrushQuickSwitch(laOperator* a, laEvent* e){
     char* id=strGetArgumentString(a->ExtraInstructionsP,"binding"); if(!id){ return LA_CANCELED; }
     int num; int ret=sscanf(id,"%d",&num); if(ret>9||ret<0){ return LA_CANCELED; }
+    OurBrush* found=0,*first=0; int set=0;
     for(OurBrush* b=Our->Brushes.pFirst;b;b=b->Item.pNext){ 
         if(b->Binding==num){
-            ourset_CurrentBrush(Our,b); laNotifyUsers("our.tools.brushes"); break;
-        } 
+            if(!first){ first=b; }
+            if(found){ ourset_CurrentBrush(Our,b); set=1; laNotifyUsers("our.tools.brushes"); break; }
+            elif(b == Our->CurrentBrush){ found = b; }
+        }
+    }
+    if(!found || (found && !set)){ found = first; }
+    if(!set && found){
+         ourset_CurrentBrush(Our,found); laNotifyUsers("our.tools.brushes");
     }
     return LA_FINISHED;
 }
@@ -1862,6 +2069,11 @@ int ourinv_ToggleErase(laOperator* a, laEvent* e){
     if(Our->Erasing){ Our->Erasing=0; }else{ Our->Erasing=1; } laNotifyUsers("our.erasing");
     return LA_FINISHED;
 }
+int ourinv_CycleSketch(laOperator* a, laEvent* e){
+    Our->SketchMode++; Our->SketchMode%=3;
+    laNotifyUsers("our.canvas"); laNotifyUsers("our.canvas.sketch_mode");
+    laMarkMemChanged(Our->CanvasSaverDummyList.pFirst);
+}
 
 void our_SmoothGlobalInput(real *x, real *y, int reset){
     if(reset){ Our->LastX=*x; Our->LastY=*y; return; }
@@ -1877,15 +2089,15 @@ int ourinv_Action(laOperator* a, laEvent* e){
     real ofx,ofy; our_GetBrushOffset(ex,Our->CurrentBrush,e->Orientation,&ofx,&ofy); ex->DownTilt = e->Orientation;
     real x,y; our_UiToCanvas(&ex->Base,e,&x,&y); x-=ofx; y-=ofy; our_SmoothGlobalInput(&x,&y,1);
     ex->CanvasLastX=x;ex->CanvasLastY=y;ex->LastPressure=-1;ex->LastTilt[0]=e->Orientation;ex->LastTilt[1]=e->Deviation;
-    ex->CanvasDownX=x; ex->CanvasDownY=y;
+    ex->CanvasDownX=x; ex->CanvasDownY=y; ex->MovedX=0; ex->MovedY=0;
     Our->ActiveTool=Our->Tool; Our->CurrentScale = 1.0f/ex->Base.ZoomX;
     Our->xmin=FLT_MAX;Our->xmax=-FLT_MAX;Our->ymin=FLT_MAX;Our->ymax=-FLT_MAX; Our->ResetBrush=1; ex->HideBrushCircle=1;
     Our->PaintProcessedEvents=0; Our->BadEventsGiveUp=0; Our->BadEventCount=0;
-    if(Our->ActiveTool==OUR_TOOL_CROP){ if(!Our->ShowBorder) return LA_FINISHED; our_StartCropping(ex); }
-    if(l->Hide || l->Transparency==1 || l->Lock){ return LA_FINISHED; }
+    if(Our->ActiveTool==OUR_TOOL_CROP){ if(!Our->ShowBorder){ ex->HideBrushCircle=0; return LA_FINISHED;} our_StartCropping(ex); }
+    if(l->Hide || l->Transparency==1 || l->Lock || (l->AsSketch && Our->SketchMode==2)){ ex->HideBrushCircle=0; return LA_FINISHED; }
     Our->LockBackground=1; laNotifyUsers("our.lock_background");
     our_EnsureEraser(e->IsEraser);
-    laHideCursor();
+    laHideCursor(); Our->ShowBrushName=0;
     return LA_RUNNING;
 }
 int ourmod_Paint(laOperator* a, laEvent* e){
@@ -1936,6 +2148,26 @@ int ourmod_Crop(laOperator* a, laEvent* e){
 
     return LA_RUNNING;
 }
+int ourmod_Move(laOperator* a, laEvent* e){
+    OurLayer* l=Our->CurrentLayer; OurCanvasDraw *ex = a->This?a->This->EndInstance:0; OurBrush* ob=Our->CurrentBrush; if(!l||!ex||!ob) return LA_CANCELED;
+    if(e->type==LA_L_MOUSE_UP || e->type==LA_R_MOUSE_DOWN || e->type==LA_ESCAPE_DOWN){
+        OurMoveUndo* undo = memAcquire(sizeof(OurMoveUndo));
+        undo->dx = ex->MovedX; undo->dy = ex->MovedY; undo->Layer = Our->CurrentLayer;
+        laFreeNewerDifferences();
+        laRecordCustomDifferences(undo,ourundo_Move,ourredo_Move,memFree); laPushDifferences("Move layer",0);
+        ex->HideBrushCircle=0; laShowCursor(); return LA_FINISHED;
+    }
+
+    if(e->type==LA_MOUSEMOVE||e->type==LA_L_MOUSE_DOWN){
+        real x,y; our_UiToCanvas(&ex->Base,e,&x,&y);
+        int movedx=0,movedy=0;
+        our_DoMoving(ex,x,y,&movedx,&movedy);
+        ex->MovedX+=movedx; ex->MovedY+=movedy;
+        laNotifyUsers("our.canvas"); laMarkMemChanged(Our->CanvasSaverDummyList.pFirst);
+    }
+
+    return LA_RUNNING;
+}
 int ourmod_Action(laOperator* a, laEvent* e){
     OurCanvasDraw *ex = a->This?a->This->EndInstance:0; if(!ex) return LA_CANCELED;
     OurLayer* l=Our->CurrentLayer; OurBrush* ob=Our->CurrentBrush;
@@ -1944,6 +2176,8 @@ int ourmod_Action(laOperator* a, laEvent* e){
         return ourmod_Paint(a,e);
     case OUR_TOOL_CROP:
         return ourmod_Crop(a,e);
+    case OUR_TOOL_MOVE:
+        return ourmod_Move(a,e);
     default: return LA_FINISHED;
     }
     return LA_RUNNING;
@@ -1970,6 +2204,12 @@ int ourchk_CropToRef(laPropPack *This, laStringSplitor *ss){ if(Our->ShowRef&&Ou
 int ourinv_CropToRef(laOperator* a, laEvent* e){
     if((!Our->ShowRef) || (!Our->ShowBorder)) return LA_FINISHED;
     real W,H,W2,H2; OUR_GET_REF_SIZE(W,H)
+    char* arg = strGetArgumentString(a->ExtraInstructionsP,"border");
+    if(strSame(arg,"outer")){
+        W+=Our->RefPaddings[0]*2; H+=Our->RefPaddings[1]*2;
+    }elif(strSame(arg,"inner")){
+        W-=Our->RefMargins[0]*2; H-=Our->RefMargins[1]*2;
+    }
     real dpc=OUR_DPC; W*=dpc; H*=dpc; W2=W/2; H2=H/2;
     Our->X=-W2; Our->W=W; Our->Y=H2; Our->H=H;
     laMarkMemChanged(Our->CanvasSaverDummyList.pFirst); laNotifyUsers("our.canvas");
@@ -2092,6 +2332,7 @@ void* ourget_our(void* unused, void* unused1){
     return Our;
 }
 void ourget_LayerTileStart(OurLayer* l, int* xy){
+    our_LayerClearEmptyTiles(l);
     our_LayerEnsureImageBuffer(l, 1); xy[0]=Our->ImageX; xy[1]=Our->ImageY+Our->ImageH;
 }
 void ourset_LayerTileStart(OurLayer* l, int* xy){
@@ -2121,6 +2362,9 @@ void ourset_LayerAlpha(OurLayer* l, real a){
 void ourset_LayerHide(OurLayer* l, int hide){
     l->Hide=hide; laNotifyUsers("our.canvas");  laMarkMemChanged(Our->CanvasSaverDummyList.pFirst);
 }
+void ourset_LayerAsSketch(OurLayer* l, int sketch){
+    l->AsSketch=sketch; laNotifyUsers("our.canvas");  laMarkMemChanged(Our->CanvasSaverDummyList.pFirst);
+}
 void ourset_LayerBlendMode(OurLayer* l, int mode){
     l->BlendMode=mode; laNotifyUsers("our.canvas");  laMarkMemChanged(Our->CanvasSaverDummyList.pFirst);
 }
@@ -2174,6 +2418,7 @@ void ourset_CurrentBrush(void* unused, OurBrush* b){
     Our->CurrentBrush=b;
     if(b->DefaultAsEraser){ Our->Erasing=1; Our->EraserID=b->Binding; if(Our->LockRadius) b->Size=Our->SaveEraserSize?Our->SaveEraserSize:r; }
     else{ Our->Erasing=0; Our->PenID=b->Binding; if(Our->LockRadius) b->Size=Our->SaveBrushSize?Our->SaveBrushSize:r; }
+    Our->ShowBrushName = 1;
     laNotifyUsers("our.tools.current_brush"); laGraphRequestRebuild();
 }
 void ourset_CurrentLayer(void* unused, OurLayer*l){
@@ -2214,6 +2459,7 @@ int ourfilter_BrushInPage(void* Unused, OurBrush* b){
     if(Our->BrushPage==3 && (b->ShowInPages&(1<<2))) return 1;
     return 0;
 }
+void ourset_ShowSketch(void* unused, int c){ Our->SketchMode=c; laNotifyUsers("our.canvas"); }
 
 int ourget_CanvasVersion(void* unused){
     return OUR_VERSION_MAJOR*100+OUR_VERSION_MINOR*10+OUR_VERSION_SUB;
@@ -2320,6 +2566,7 @@ void ourRegisterEverything(){
 
     laCreateOperatorType("OUR_show_splash","Show Splash","Show splash screen",0,0,0,ourinv_ShowSplash,0,0,0);
     laCreateOperatorType("OUR_new_layer","New Layer","Create a new layer",0,0,0,ourinv_NewLayer,0,'+',0);
+    laCreateOperatorType("OUR_duplicate_layer","Duplicate Layer","Duplicate a layer",0,0,0,ourinv_DuplicateLayer,0,'⎘',0);
     laCreateOperatorType("OUR_remove_layer","Remove Layer","Remove this layer",0,0,0,ourinv_RemoveLayer,0,U'🗴',0);
     laCreateOperatorType("OUR_move_layer","Move Layer","Remove this layer",0,0,0,ourinv_MoveLayer,0,0,0);
     laCreateOperatorType("OUR_merge_layer","Merge Layer","Merge this layer with the layer below it",ourchk_MergeLayer,0,0,ourinv_MergeLayer,0,0,0);
@@ -2340,6 +2587,7 @@ void ourRegisterEverything(){
     laAddEnumItemAs(p,"CANVAS","Follow Canvas","Transform the pixels into current canvas interpretation",OUR_PNG_READ_OUTPUT_CANVAS,0);
     laAddEnumItemAs(p,"LINEAR_SRGB","Linear sRGB","Write sRGB pixels values into canvas regardless of the canvas interpretation",OUR_PNG_READ_OUTPUT_LINEAR_SRGB,0);
     laAddEnumItemAs(p,"LINEAR_CLAY","Linear Clay","Write Clay (AdobeRGB 1998 compatible) pixels values into canvas regardless of the canvas interpretation",OUR_PNG_READ_OUTPUT_LINEAR_CLAY,0);
+    laAddIntProperty(pc,"offsets","Offsets","Offsets of the imported layer (0 for default)",0,"X,Y",0,0,0,0,0,0,offsetof(OurPNGReadExtra,Offsets),0,0,2,0,0,0,0,0,0,0,0);
 
     laCreateOperatorType("OUR_new_brush","New Brush","Create a new brush",0,0,0,ourinv_NewBrush,0,'+',0);
     laCreateOperatorType("OUR_remove_brush","Remove Brush","Remove this brush",0,0,0,ourinv_RemoveBrush,0,U'🗴',0);
@@ -2360,6 +2608,7 @@ void ourRegisterEverything(){
     laAddEnumItemAs(p,"CLAY","Clay","Convert pixels into non-linear Clay (AdobeRGB 1998 compatible)",OUR_EXPORT_COLOR_MODE_CLAY,0);
 
     laCreateOperatorType("OUR_toggle_erasing","Toggle Erasing","Toggle erasing",0,0,0,ourinv_ToggleErase,0,0,0);
+    laCreateOperatorType("OUR_cycle_sketch","Cycle Sketches","Cycle sketch layer display mode",0,0,0,ourinv_CycleSketch,0,0,0);
 
     laCreateOperatorType("OUR_crop_to_ref","Crop To Ref","Crop to reference lines",ourchk_CropToRef,0,0,ourinv_CropToRef,0,0,0);
 
@@ -2371,6 +2620,7 @@ void ourRegisterEverything(){
     laCreateOperatorType("OUR_clear_empty_tiles","Clear Empty Tiles","Clear empty tiles in this image",0,0,0,ourinv_ClearEmptyTiles,0,U'🧹',0);
 
     laRegisterUiTemplate("panel_canvas", "Canvas", ourui_CanvasPanel, 0, 0,"Our Paint", GL_RGBA16F,25,25);
+    laRegisterUiTemplate("panel_thumbnail", "Thumbnail", ourui_ThumbnailPanel, 0, 0, 0, GL_RGBA16F,25,25);
     laRegisterUiTemplate("panel_layers", "Layers", ourui_LayersPanel, 0, 0,0, 0,10,15);
     laRegisterUiTemplate("panel_tools", "Tools", ourui_ToolsPanel, 0, 0,0, 0,10,20);
     laRegisterUiTemplate("panel_brushes", "Brushes", ourui_BrushesPanel, 0, 0,0, 0,10,15);
@@ -2391,6 +2641,7 @@ void ourRegisterEverything(){
     p=laAddEnumProperty(pc,"tool","Tool","Tool to use on the canvas",0,0,0,0,0,offsetof(OurPaint,Tool),0,ourset_Tool,0,0,0,0,0,0,0,0);
     laAddEnumItemAs(p,"PAINT","Paint","Paint stuff on the canvas",OUR_TOOL_PAINT,U'🖌');
     laAddEnumItemAs(p,"CROP","Cropping","Crop the focused region",OUR_TOOL_CROP,U'🖼');
+    laAddEnumItemAs(p,"MOVE","Moving","Moving the layer",OUR_TOOL_MOVE,U'🤚');
     p=laAddEnumProperty(pc,"lock_background","Lock background","Lock background color to prevent accidental changes",0,0,0,0,0,offsetof(OurPaint,LockBackground),0,0,0,0,0,0,0,0,0,0);
     laAddEnumItemAs(p,"NONE","Unlocked","You can change background color",0,0);
     laAddEnumItemAs(p,"LOCK","Locked","Background color is locked to prevent accidental changes",1,U'🔏');
@@ -2412,6 +2663,9 @@ void ourRegisterEverything(){
     p=laAddEnumProperty(pc,"enable_brush_circle","Brush Circle","Enable brush circle when hovering",LA_WIDGET_ENUM_HIGHLIGHT,0,0,0,0,offsetof(OurPaint,EnableBrushCircle),0,0,0,0,0,0,0,0,0,0);
     laAddEnumItemAs(p,"FALSE","No","Don't show brush circle",0,0);
     laAddEnumItemAs(p,"TRUE","Yes","Show brush circle on hover",1,0);
+    p=laAddEnumProperty(pc,"show_brush_name","Show brush name","Show brush name next to the cursor",0,0,0,0,0,offsetof(OurPaint,ShowBrushName),0,0,0,0,0,0,0,0,0,LA_READ_ONLY|LA_UDF_IGNORE);
+    laAddEnumItemAs(p,"FALSE","No","Don't show brush name next to the cursor",0,0);
+    laAddEnumItemAs(p,"TRUE","Yes","Show brush name next to the cursor",1,0);
     p=laAddEnumProperty(pc,"allow_none_pressure","Allow Non-pressure","Allow non-pressure events, this enables mouse painting.",LA_WIDGET_ENUM_HIGHLIGHT,0,0,0,0,offsetof(OurPaint,AllowNonPressure),0,0,0,0,0,0,0,0,0,0);
     laAddEnumItemAs(p,"FALSE","No","Don't allow non-pressure device inputs",0,0);
     laAddEnumItemAs(p,"TRUE","Yes","Allow non-pressure device inputs such as a mouse",1,0);
@@ -2555,7 +2809,11 @@ void ourRegisterEverything(){
     laAddFloatProperty(pc,"ref_paddings","Paddings","Paddings of the reference block",0,"L/R,T/B","cm",0,0,0,0,0,offsetof(OurPaint,RefPaddings),0,0,2,0,0,0,0,ourset_RefPaddings,0,0,0);
     laAddFloatProperty(pc,"ref_middle_margin","Middle Margin","Margin in the middle of the spread",0,0,"cm",0,0,0,0,0,offsetof(OurPaint,RefMargins[2]),0,ourset_RefMiddleMargin,0,0,0,0,0,0,0,0,0);
     laAddIntProperty(pc,"ref_biases","Reference Biases","Position biases when reading reference block",0,0,0,0,0,0,0,0,offsetof(OurPaint,RefBiases),0,0,0,0,0,0,0,0,0,0,0);
-    
+    p=laAddEnumProperty(pc,"sketch_mode","Sketch Mode","Show sketch layers differently",0,0,0,0,0,offsetof(OurPaint,SketchMode),0,ourset_ShowSketch,0,0,0,0,0,0,0,0);
+    laAddEnumItemAs(p,"NORMAL","Normal","Show sketch layers as normal layers",0,0);
+    laAddEnumItemAs(p,"FULL","Full","Show sketch layers in full opacity",1,0);
+    laAddEnumItemAs(p,"NONE","None","Show double page spread",2,0);
+
     pc=laAddPropertyContainer("our_layer","Our Layer","OurPaint layer",0,0,sizeof(OurLayer),0,0,1);
     laPropContainerExtraFunctions(pc,ourbeforefree_Layer,ourbeforefree_Layer,0,0,0);
     laAddStringProperty(pc,"name","Name","Name of the layer",0,0,0,0,1,offsetof(OurLayer,Name),0,0,0,0,LA_AS_IDENTIFIER);
@@ -2575,7 +2833,11 @@ void ourRegisterEverything(){
     laAddRawProperty(pc,"image","Image","The image data of this tile",0,0,ourget_LayerImage,ourset_LayerImage,LA_UDF_ONLY);
     laAddOperatorProperty(pc,"move","Move","Move Layer","OUR_move_layer",0,0);
     laAddOperatorProperty(pc,"remove","Remove","Remove layer","OUR_remove_layer",U'🗴',0);
-    laAddOperatorProperty(pc,"merge","Merge","Merge Layer","OUR_merge_layer",U'🠳',0);
+    laAddOperatorProperty(pc,"merge","Merge","Merge layer","OUR_merge_layer",U'🠳',0);
+    laAddOperatorProperty(pc,"duplicate","Duplicate","Duplicate layer","OUR_duplicate_layer",U'⎘',0);
+    p=laAddEnumProperty(pc,"as_sketch","As Sketch","As sketch layer (for quick toggle)",0,0,0,0,0,offsetof(OurLayer,AsSketch),0,ourset_LayerAsSketch,0,0,0,0,0,0,0,0);
+    laAddEnumItemAs(p,"NORMAL","Normal","Layer is normal",0,U'🖌');
+    laAddEnumItemAs(p,"SKETCH","Sketch","Layer is a sketch layer",1,U'🖉');
     
     laCanvasTemplate* ct=laRegisterCanvasTemplate("our_CanvasDraw", "our_canvas", ourextramod_Canvas, our_CanvasDrawCanvas, our_CanvasDrawOverlay, our_CanvasDrawInit, la_CanvasDestroy);
     pc = laCanvasHasExtraProps(ct,sizeof(OurCanvasDraw),2);
@@ -2599,6 +2861,16 @@ void ourRegisterEverything(){
     laAssignNewKey(km, 0, "OUR_brush_resize", 0, 0, LA_KEY_DOWN, '[', "direction=smaller");
     laAssignNewKey(km, 0, "OUR_brush_resize", 0, 0, LA_KEY_DOWN, ']', "direction=bigger");
     laAssignNewKey(km, 0, "OUR_toggle_erasing", 0, 0, LA_KEY_DOWN, 'e', 0);
+    laAssignNewKey(km, 0, "OUR_cycle_sketch", 0, 0, LA_KEY_DOWN, 's', 0);
+
+    laNewCustomSignal("our.pick",OUR_SIGNAL_PICK);
+    laNewCustomSignal("our.move",OUR_SIGNAL_MOVE);
+    laNewCustomSignal("our.toggle_erasing",OUR_SIGNAL_TOGGLE_ERASING);
+    laNewCustomSignal("our.toggle_sketch",OUR_SIGNAL_TOGGLE_SKETCH);
+    laNewCustomSignal("our.zoom_in",OUR_SIGNAL_ZOOM_IN);
+    laNewCustomSignal("our.zoom_out",OUR_SIGNAL_ZOOM_OUT);
+    laNewCustomSignal("our.bursh_bigger",OUR_SIGNAL_BRUSH_BIGGER);
+    laNewCustomSignal("our.brush_smaller",OUR_SIGNAL_BRUSH_SMALLER);
 
     laAssignNewKey(km, 0, "LA_undo", 0, LA_KEY_CTRL, LA_KEY_DOWN, ']', 0);
     laAssignNewKey(km, 0, "LA_redo", 0, LA_KEY_CTRL, LA_KEY_DOWN, '[', 0);

+ 20 - 1
ourpaint.h

@@ -55,6 +55,16 @@ extern const char OUR_COMPOSITION_SHADER[];
 
 #define OUR_PAINT_NAME_STRING "Our Paint v0.2"
 
+#define OUR_SIGNAL_PICK 1
+#define OUR_SIGNAL_MOVE 2
+#define OUR_SIGNAL_PICK 3
+#define OUR_SIGNAL_TOGGLE_ERASING 4
+#define OUR_SIGNAL_ZOOM_IN 5
+#define OUR_SIGNAL_ZOOM_OUT 6
+#define OUR_SIGNAL_BRUSH_BIGGER 7
+#define OUR_SIGNAL_BRUSH_SMALLER 8
+#define OUR_SIGNAL_TOGGLE_SKETCH 9
+
 STRUCTURE(OurCanvasDraw){
     laCanvasExtra Base;
     int HideBrushCircle;
@@ -65,6 +75,7 @@ STRUCTURE(OurCanvasDraw){
     real LastPressure;
     real LastTilt[2];
     real LastTwist;
+    int MovedX,MovedY;
 };
 
 #define OUR_DPC (600*0.3937007874)
@@ -95,6 +106,7 @@ STRUCTURE(OurLayer){
     int Lock;
     int Hide;
     int BlendMode;
+    int AsSketch;
     OurTexTile** TexTiles[OUR_TILES_PER_ROW];
 };
 
@@ -247,9 +259,14 @@ STRUCTURE(OurUndo){
     OurLayer* Layer;
     laListHandle Tiles;
 };
+STRUCTURE(OurMoveUndo){
+    OurLayer* Layer;
+    int dx,dy;
+};
 
 #define OUR_TOOL_PAINT 0
 #define OUR_TOOL_CROP 1
+#define OUR_TOOL_MOVE 2
 
 #define OUR_PNG_READ_INPUT_FLAT 0
 #define OUR_PNG_READ_INPUT_ICC  1
@@ -282,6 +299,7 @@ STRUCTURE(OurPNGReadExtra){
     int HasProfile;
     int InputMode;
     int OutputMode;
+    int Offsets[2];
 };
 STRUCTURE(OurPNGWriteExtra){
     int Confirming;
@@ -336,11 +354,12 @@ STRUCTURE(OurPaint){
     int BadEventsLimit,BadEventCount,BadEventsGiveUp;
 
     int LockRadius;
-    int EnableBrushCircle; int EventHasTwist; real EventTwistAngle;
+    int EnableBrushCircle,ShowBrushName; int EventHasTwist; real EventTwistAngle;
     int DefaultBitDepth;
     int DefaultColorProfile;
     int PaintUndoLimit;
     int SpectralMode;
+    int SketchMode;
 
     tnsTexture* SmudgeTexture;
     GLuint CanvasShader;      GLuint CanvasProgram;