*/}}
Browse Source

png save etc

Yiming Wu 1 year ago
parent
commit
c1703cbb4e
4 changed files with 198 additions and 5 deletions
  1. 6 0
      CMakeLists.txt
  2. 17 0
      FindLCMS2.cmake
  3. 168 1
      ouroperations.c
  4. 7 4
      ourpaint.h

+ 6 - 0
CMakeLists.txt

@@ -1,7 +1,11 @@
 cmake_minimum_required(VERSION 3.1)
 project(OurPaint)
 
+set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR})
+
 find_package(lagui REQUIRED)
+find_package(PNG REQUIRED)
+find_package(LCMS2 REQUIRED)
 
 add_definitions(-w)
 
@@ -17,4 +21,6 @@ add_executable(OurPaint ${OurPaintFiles})
 
 target_link_libraries(OurPaint
     ${LAGUI_SHARED_LIBS}
+    ${PNG_LIBRARY}
+    ${LCMS2_LIBRARIES}
 )

+ 17 - 0
FindLCMS2.cmake

@@ -0,0 +1,17 @@
+find_path(LCMS2_INCLUDE_DIR lcms2.h PATHS /usr/include /usr/local/include /opt/include /opt/local/include)
+
+set(LCMS2_NAMES ${LCMS2_NAMES} lcms2 liblcms2 liblcms2_static)
+
+find_library(LCMS2_LIBRARY NAMES ${LCMS2_NAMES} )
+
+mark_as_advanced(LCMS2_INCLUDE_DIR LCMS2_LIBRARY)
+
+# handle the QUIETLY and REQUIRED arguments and set LCMS2_FOUND to TRUE if
+# all listed variables are TRUE
+include(FindPackageHandleStandardArgs)
+FIND_PACKAGE_HANDLE_STANDARD_ARGS(LCMS2  DEFAULT_MSG  LCMS2_LIBRARY  LCMS2_INCLUDE_DIR)
+
+if(LCMS2_FOUND)
+  set( LCMS2_INCLUDE_DIRS ${LCMS2_INCLUDE_DIR})
+  set( LCMS2_LIBRARIES ${LCMS2_LIBRARY} )
+endif()

+ 168 - 1
ouroperations.c

@@ -1,4 +1,6 @@
 #include "ourpaint.h"
+#include "png.h"
+#include "lcms2.h"
 
 OurPaint *Our;
 extern LA MAIN;
@@ -55,6 +57,35 @@ void main() {\n\
 }\n\
 ";
 
+void our_InitsRGBProfile(int Linear, void** ptr, int* psize, char* copyright, char* manufacturer, char* description){
+    cmsCIExyYTRIPLE srgb_primaries_pre_quantized = { {0.639998686, 0.330010138, 1.0}, {0.300003784, 0.600003357, 1.0}, {0.150002046, 0.059997204, 1.0} };
+    cmsCIExyY d65_srgb_adobe_specs = {0.3127, 0.3290, 1.0};
+    cmsToneCurve*tonecurve; cmsToneCurve*curve[3];
+    if(Linear){ tonecurve = cmsBuildGamma (NULL, 1.0f); }
+    else{
+        cmsFloat64Number srgb_parameters[5] = { 2.4, 1.0 / 1.055,  0.055 / 1.055, 1.0 / 12.92, 0.04045 };
+        tonecurve=cmsBuildParametricToneCurve(NULL, 4, srgb_parameters);
+    }
+    curve[0] = curve[1] = curve[2] = tonecurve;
+    cmsHPROFILE profile4 = cmsCreateRGBProfile (&d65_srgb_adobe_specs, &srgb_primaries_pre_quantized, curve);
+    cmsMLU *copy = cmsMLUalloc(NULL, 1);
+    cmsMLUsetASCII(copy, "en", "US", copyright);
+    cmsWriteTag(profile4, cmsSigCopyrightTag, copy);
+    cmsMLU* manu = cmsMLUalloc(NULL, 1);
+    cmsMLUsetASCII(manu, "en", "US", manufacturer);
+    cmsWriteTag(profile4, cmsSigDeviceMfgDescTag, manu);
+    cmsMLU *desc = cmsMLUalloc(NULL, 1);
+    cmsMLUsetASCII(desc, "en", "US", description);
+    cmsWriteTag(profile4, cmsSigProfileDescriptionTag, desc);
+    cmsSaveProfileToMem(profile4, 0, psize);
+    (*ptr)=calloc(1,*psize); cmsSaveProfileToMem(profile4, *ptr, psize);
+    cmsMLUfree(copy); cmsMLUfree(manu); cmsMLUfree(desc); cmsFreeToneCurve(tonecurve); cmsCloseProfile(profile4);
+}
+void our_InitColorProfiles(){
+    char* manu="sRGB chromaticities from A Standard Default Color Space for the Internet - sRGB, http://www.w3.org/Graphics/Color/sRGB; and http://www.color.org/specification/ICC1v43_2010-12.pdf";
+    our_InitsRGBProfile(1,&Our->icc_LinearsRGB,&Our->iccsize_LinearsRGB,"Copyright Yiming 2022.",manu,"Yiming's linear sRGB icc profile.");
+    our_InitsRGBProfile(0,&Our->icc_sRGB,&Our->iccsize_sRGB,"Copyright Yiming 2022.",manu,"Yiming's sRGB icc profile.");
+}
 
 void ourui_CanvasPanel(laUiList *uil, laPropPack *This, laPropPack *DetachedProps, laColumn *UNUSED, int context){
     laColumn* c=laFirstColumn(uil);
@@ -204,7 +235,6 @@ void our_CanvasDrawCropping(OurCanvasDraw* ocd){
     }
 }
 
-
 void our_CanvasDrawInit(laUiItem* ui){
     ui->Extra=memAcquireHyper(sizeof(OurCanvasDraw));
     laFirstColumn(laAddTabPage(ui, "New Group"));
@@ -303,6 +333,106 @@ void our_LayerEnsureTiles(OurLayer* ol, real xmin,real xmax, real ymin,real ymax
     }
     *tl=l; *tr=r; *tu=u; *tb=b;
 }
+void our_TileTextureToImage(OurTexTile* ot, int SX, int SY){
+    int bufsize=sizeof(uint16_t)*OUR_TEX_TILE_W_USE*OUR_TEX_TILE_W_USE*4;
+    ot->Data=malloc(bufsize); int seam=OUR_TEX_TILE_SEAM; int width=OUR_TEX_TILE_W_USE;
+    
+    tnsBindTexture(ot->Texture);
+    glPixelStorei(GL_PACK_ALIGNMENT, 2);
+    glGetTextureSubImage(ot->Texture->GLTexHandle, 0, seam, seam, 0, width, width,1, GL_RGBA, GL_UNSIGNED_SHORT, bufsize, ot->Data);
+    int acc=0,read=0;
+    for(int row=0;row<OUR_TEX_TILE_W_USE;row++){
+        memcpy(&Our->ImageBuffer[((SY+row)*Our->ImageW+SX)*4],&ot->Data[(row*OUR_TEX_TILE_W_USE)*4],sizeof(uint16_t)*4*OUR_TEX_TILE_W_USE);
+    }
+    printf("%d %d\n",acc,read);
+    free(ot->Data);
+}
+int our_LayerEnsureImageBuffer(OurLayer* ol){
+    int l=1000,r=-1000,u=-1000,b=1000; int any=0;
+    for(int row=0;row<OUR_TEX_TILES_PER_ROW;row++){ if(!ol->TexTiles[row]) continue;
+        if(row<b) b=row; if(row>u) u=row;
+        for(int col=0;col<OUR_TEX_TILES_PER_ROW;col++){ if(!ol->TexTiles[row][col]) continue;
+            if(col<l) l=col; if(col>r) r=col; any++;
+        }
+    }
+    if(!any) return 0;
+    Our->ImageW = OUR_TEX_TILE_W_USE*(r-l+1); Our->ImageH = OUR_TEX_TILE_W_USE*(u-b+1);
+    Our->ImageX =((real)l-OUR_TEX_TILE_CTR-0.5)*OUR_TEX_TILE_W_USE; Our->ImageY=((real)b-OUR_TEX_TILE_CTR-0.5)*OUR_TEX_TILE_W_USE;
+    if(Our->ImageBuffer) free(Our->ImageBuffer);
+    Our->ImageBuffer = calloc(Our->ImageW*4,Our->ImageH*sizeof(uint16_t));
+    return 1;
+}
+void our_LayerToImageBuffer(OurLayer* ol){
+    for(int row=0;row<OUR_TEX_TILES_PER_ROW;row++){ if(!ol->TexTiles[row]) continue;
+        for(int col=0;col<OUR_TEX_TILES_PER_ROW;col++){ if(!ol->TexTiles[row][col]) continue;
+            int sx=((real)col-OUR_TEX_TILE_CTR-0.5)*OUR_TEX_TILE_W_USE,sy=((real)row-OUR_TEX_TILE_CTR-0.5)*OUR_TEX_TILE_W_USE;
+            our_TileTextureToImage(ol->TexTiles[row][col], sx-Our->ImageX, sy-Our->ImageY);
+        }
+    }
+}
+int our_LayerExportPNG(OurLayer* l, FILE* fp){
+    if(!l||!fp) return 0;
+
+    if(!our_LayerEnsureImageBuffer(l)) return 0;
+
+    our_LayerToImageBuffer(l);
+
+    png_structp png_ptr=png_create_write_struct(PNG_LIBPNG_VER_STRING,0,0,0);
+    png_infop info_ptr = png_create_info_struct(png_ptr);
+    png_init_io(png_ptr, fp);
+    
+    png_set_IHDR(png_ptr, info_ptr,Our->ImageW,Our->ImageH,16,PNG_COLOR_TYPE_RGBA,PNG_INTERLACE_NONE,PNG_COMPRESSION_TYPE_BASE,PNG_FILTER_TYPE_BASE);
+    //png_set_sRGB_gAMA_and_cHRM(png_ptr,info_ptr,PNG_sRGB_INTENT_PERCEPTUAL);
+    //png_set_iCCP(png_ptr,info_ptr,"LA_PROFILE",PNG_COMPRESSION_TYPE_BASE,Our->icc_sRGB,Our->iccsize_sRGB);
+    png_set_iCCP(png_ptr,info_ptr,"LA_PROFILE",PNG_COMPRESSION_TYPE_BASE,Our->icc_LinearsRGB,Our->iccsize_LinearsRGB);
+    png_set_gAMA(png_ptr,info_ptr,0.45455);
+    //don't set alpha mode for internal data so read and write would be the same.
+
+    png_write_info(png_ptr, info_ptr);
+    png_set_swap(png_ptr);
+
+    for(int i=0;i<Our->ImageH;i++){
+        png_write_row(png_ptr, (png_const_bytep)&Our->ImageBuffer[Our->ImageW*(Our->ImageH-i)*4]);
+    }
+
+    png_write_end(png_ptr, info_ptr);
+    png_destroy_write_struct(&png_ptr, &info_ptr);
+
+    return 1;
+}
+int our_LayerImportPNG(OurLayer* l, FILE* fp){
+    if(!fp) return 0;
+
+    if(!l) l=our_NewLayer("Imported");
+
+    if(!our_LayerEnsureImageBuffer(l)) return 0;
+
+    our_LayerToImageBuffer(l);
+
+    png_structp png_ptr=png_create_write_struct(PNG_LIBPNG_VER_STRING,0,0,0);
+    png_infop info_ptr = png_create_info_struct(png_ptr);
+    png_init_io(png_ptr, fp);
+    
+    png_set_IHDR(png_ptr, info_ptr,Our->ImageW,Our->ImageH,16,PNG_COLOR_TYPE_RGBA,PNG_INTERLACE_NONE,PNG_COMPRESSION_TYPE_BASE,PNG_FILTER_TYPE_BASE);
+    //png_set_sRGB_gAMA_and_cHRM(png_ptr,info_ptr,PNG_sRGB_INTENT_PERCEPTUAL);
+    //png_set_iCCP(png_ptr,info_ptr,"LA_PROFILE",PNG_COMPRESSION_TYPE_BASE,Our->icc_sRGB,Our->iccsize_sRGB);
+    png_set_iCCP(png_ptr,info_ptr,"LA_PROFILE",PNG_COMPRESSION_TYPE_BASE,Our->icc_LinearsRGB,Our->iccsize_LinearsRGB);
+    png_set_gAMA(png_ptr,info_ptr,0.45455);
+    //don't set alpha mode for internal data so read and write would be the same.
+
+    png_write_info(png_ptr, info_ptr);
+    png_set_swap(png_ptr);
+
+    for(int i=0;i<Our->ImageH;i++){
+        png_write_row(png_ptr, (png_const_bytep)&Our->ImageBuffer[Our->ImageW*(Our->ImageH-i)*4]);
+    }
+
+    png_write_end(png_ptr, info_ptr);
+    png_destroy_write_struct(&png_ptr, &info_ptr);
+
+    return 1;
+}
+
 void our_UiToCanvas(laCanvasExtra* ex, laEvent*e, real* x, real *y){
     *x = (real)((real)e->x - (real)(ex->ParentUi->R - ex->ParentUi->L) / 2 - ex->ParentUi->L) * ex->ZoomX + ex->PanX;
     *y = (real)((real)(ex->ParentUi->B - ex->ParentUi->U) / 2 - (real)e->y + ex->ParentUi->U) * ex->ZoomY + ex->PanY;
@@ -464,6 +594,25 @@ int ourinv_MoveLayer(laOperator* a, laEvent* e){
     elif(l->Item.pNext){ lstMoveDown(&Our->Layers, l); laNotifyUsers("our.canvas.layers"); }
     return LA_FINISHED;
 }
+int ourinv_ExportLayer(laOperator* a, laEvent* e){
+    OurLayer* ol=a->This?a->This->EndInstance:0; if(!ol) ol=Our->CurrentLayer; if(!ol) return LA_FINISHED;
+    laInvoke(a, "LA_file_dialog", e, 0, 0, 0);
+    return LA_RUNNING;
+}
+int ourmod_ExportLayer(laOperator* a, laEvent* e){
+    OurLayer* ol=a->This?a->This->EndInstance:0; if(!ol) ol=Our->CurrentLayer; if(!ol) return LA_FINISHED;
+    if (a->ConfirmData){
+        if (a->ConfirmData->StrData){
+            FILE* fp=fopen(a->ConfirmData->StrData,"wb");
+            if(!fp) return LA_FINISHED;
+            our_LayerExportPNG(Our->CurrentLayer, fp);
+            fclose(fp);
+        }
+        return LA_FINISHED;
+    }
+    return LA_RUNNING;
+}
+
 int ourinv_NewBrush(laOperator* a, laEvent* e){
     our_NewBrush("Our Brush",15,0.95,9,0.5,0.5,5,0,0,0,0); laNotifyUsers("our.brushes");
     return LA_FINISHED;
@@ -589,12 +738,26 @@ void ourset_CanvasPosition(void* unused, int* xy){
     laAddEnumItemAs(p,"NONE","None","Not using pressure",0,0);\
     laAddEnumItemAs(p,"ENABLED","Enabled","Using pressure",1,0);
 
+void ourui_MenuButtons(laUiList *uil, laPropPack *pp, laPropPack *actinst, laColumn *extracol, int context){
+    laUiList *muil; laColumn *mc,*c = laFirstColumn(uil);
+    muil = laMakeMenuPage(uil, c, "File");{
+        mc = laFirstColumn(muil);
+        laShowLabel(muil, mc, "Our Paint", 0, 0);
+        laShowItem(muil, mc, 0, "OUR_export_layer");
+        laui_DefaultMenuButtonsFileEntries(muil,pp,actinst,extracol,0);
+    }
+    muil = laMakeMenuPage(uil, c, "Options"); {
+        mc = laFirstColumn(muil); laui_DefaultMenuButtonsOptionEntries(muil,pp,actinst,extracol,0);
+    }
+}
+
 void ourRegisterEverything(){
     laPropContainer* pc; laKeyMapper* km; laProp* p;
 
     laCreateOperatorType("OUR_new_layer","New Layer","Create a new layer",0,0,0,ourinv_NewLayer,0,'+',0);
     laCreateOperatorType("OUR_remove_layer","Remove Layer","Remove this layer",0,0,0,ourinv_RemoveLayer,0,L'🗴',0);
     laCreateOperatorType("OUR_move_layer","Move Layer","Remove this layer",0,0,0,ourinv_MoveLayer,0,0,0);
+    laCreateOperatorType("OUR_export_layer","Export Layer","Export this layer",0,0,0,ourinv_ExportLayer,ourmod_ExportLayer,L'🖫',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,L'🗴',0);
     laCreateOperatorType("OUR_move_brush","Move Brush","Remove this brush",0,0,0,ourinv_MoveBrush,0,0,0);
@@ -665,6 +828,8 @@ void ourRegisterEverything(){
     laAssignNewKey(km, 0, "LA_2d_view_move", LA_KM_SEL_UI_EXTRA, 0, LA_M_MOUSE_DOWN, 0, 0);
     laAssignNewKey(km, 0, "OUR_action", LA_KM_SEL_UI_EXTRA, 0, LA_L_MOUSE_DOWN, 0, 0);
     laAssignNewKey(km, 0, "OUR_pick", LA_KM_SEL_UI_EXTRA, 0, LA_R_MOUSE_DOWN, 0, 0);
+
+    laSetMenuBarTemplates(ourui_MenuButtons, laui_DefaultMenuExtras, "OurPaint v0.1");
 }
 
 
@@ -673,6 +838,8 @@ void ourInit(){
 
     ourRegisterEverything();
 
+    our_InitColorProfiles();
+
     char error[1024]; int status;
 
     Our->SmudgeTexture=tnsCreate2DTexture(GL_RGBA16,256,1,0);

+ 7 - 4
ourpaint.h

@@ -28,12 +28,9 @@ STRUCTURE(OurCanvasDraw){
 #define OUR_TEX_TILE_SEAM 12
 #define OUR_TEX_TILE_W_USE (OUR_TEX_TILE_W-OUR_TEX_TILE_SEAM*2)
 
-STRUCTURE(OurTile){
-    int X,Y; // with offset so not neccessarily n*OUR_TILE_W
-    void* Data;
-};
 STRUCTURE(OurTexTile){
     tnsTexture* Texture;
+    uint16_t* Data;
 };
 STRUCTURE(OurLayer){
     laListItem Item;
@@ -96,6 +93,12 @@ STRUCTURE(OurPaint){
     real CurrentColor[4];
     real BackgroundColor[3];
     real BorderAlpha;
+
+    uint16_t *ImageBuffer;
+    int ImageW,ImageH,ImageX,ImageY;
+
+    void* icc_LinearsRGB; int iccsize_LinearsRGB;
+    void* icc_sRGB; int iccsize_sRGB;
 };
 
 void ourInit();