Subversion Repositories filter_foundry

Rev

Rev 227 | Rev 232 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | RSS feed

  1. /*
  2.     This file is part of "Filter Foundry", a filter plugin for Adobe Photoshop
  3.     Copyright (C) 2003-2009 Toby Thain, toby@telegraphics.com.au
  4.     Copyright (C) 2018-2021 Daniel Marschall, ViaThinkSoft
  5.  
  6.     This program is free software; you can redistribute it and/or modify
  7.     it under the terms of the GNU General Public License as published by
  8.     the Free Software Foundation; either version 2 of the License, or
  9.     (at your option) any later version.
  10.  
  11.     This program is distributed in the hope that it will be useful,
  12.     but WITHOUT ANY WARRANTY; without even the implied warranty of
  13.     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  14.     GNU General Public License for more details.
  15.  
  16.     You should have received a copy of the GNU General Public License
  17.     along with this program; if not, write to the Free Software
  18.     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  19. */
  20.  
  21. //#include <stdio.h>
  22. //#include <sound.h>
  23.  
  24. #include "ff.h"
  25.  
  26. #include "node.h"
  27. #include "funcs.h"
  28. #include "y.tab.h"
  29. #include "scripting.h"
  30. #include <math.h>
  31.  
  32. struct node *tree[4];
  33. char *err[4];
  34. int errpos[4],errstart[4],nplanes,cnvused,chunksize,toprow;
  35. value_type slider[8],cell[NUM_CELLS],map[4][0x100];
  36. char *expr[4];
  37. // long maxSpace;
  38. globals_t *gdata;
  39. FilterRecordPtr gpb;
  40.  
  41. #ifdef MAC_ENV
  42.         #define HINSTANCE HANDLE
  43.         #define hDllInstance NULL /* fake this Windows-only global */
  44. #endif
  45.  
  46. #ifdef WIN_ENV
  47. #include "manifest.h"
  48. #endif
  49.  
  50. extern struct sym_rec predefs[];
  51. extern int nplanes,varused[];
  52.  
  53. int checkandinitparams(Handle params);
  54.  
  55. // MPW MrC requires prototype
  56. DLLEXPORT MACPASCAL
  57. void ENTRYPOINT(short selector,FilterRecordPtr pb,intptr_t *data,short *result);
  58.  
  59. DLLEXPORT MACPASCAL
  60. void ENTRYPOINT(short selector, FilterRecordPtr pb, intptr_t *data, short *result){
  61.         static Boolean wantdialog = false;
  62.         static Boolean premiereWarnedOnce = false;
  63.         OSErr e = noErr;
  64.         char *reason;
  65. #ifdef WIN_ENV
  66.         // For Windows, we use an activation context to enforce that our Manifest resource will
  67.         // be used. This allows us to use Visual Styles, even if the host application does not
  68.         // support it.
  69.         ManifestActivationCtx manifestVars;
  70.         BOOL activationContextUsed;
  71. #endif
  72.  
  73.         /*
  74.         char* s = (char*)malloc(512);
  75.         sprintf(s, "Host signature: '%c%c%c%c' (%d)\nMaxSpace32 = %d\nMaxSpace64 = %lld\nNum buffer procs: %d", (pb->hostSig >> 24) & 0xFF, (pb->hostSig >> 16) & 0xFF, (pb->hostSig >> 8) & 0xFF, pb->hostSig & 0xFF, pb->hostSig, pb->maxSpace, pb->maxSpace64, pb->bufferProcs->numBufferProcs);
  76.         simplealert(s);
  77.         */
  78.  
  79.         if (pb->hostSig == HOSTSIG_ADOBE_PREMIERE) {
  80.                 // DM 19.07.2021 : Tried running the 8BF file in Adobe Premiere 5 (yes, that's possible,
  81.                 // and there is even a FilterFactory for Premeire!),
  82.                 // but it crashes in evalpixel() where there is write-access to the "outp".
  83.                 // Probably the canvas structure is different (maybe it contains frames to achieve transitions?)
  84.                 if (!premiereWarnedOnce) {
  85.                         simplealert("This version of Filter Foundry is not compatible with Adobe Premiere!");
  86.                 }
  87.                 premiereWarnedOnce = true;
  88.                 *result = filterBadParameters;
  89.                 return;
  90.         }
  91.  
  92. #ifdef WIN_ENV
  93.         activationContextUsed = ActivateManifest((HMODULE)hDllInstance, 1, &manifestVars);
  94. #endif
  95.  
  96.         if(selector != filterSelectorAbout && !*data){
  97.                 BufferID tempId;
  98.                 if( (*result = PS_BUFFER_ALLOC(sizeof(globals_t), &tempId)) ) {
  99. #ifdef WIN_ENV
  100.                         if (activationContextUsed) DeactivateManifest(&manifestVars);
  101. #endif
  102.                         return;
  103.                 }
  104.                 *data = (intptr_t)PS_BUFFER_LOCK(tempId, true);
  105.                 gdata = (globals_t*)*data;
  106.                 gdata->standalone = gdata->parmloaded = false;
  107.         } else {
  108.                 gdata = (globals_t*)*data;
  109.         }
  110.  
  111.         EnterCodeResource();
  112.  
  113.         gpb = pb;
  114.  
  115.         nplanes = MIN(pb->planes,4);
  116.  
  117.         switch (selector){
  118.         case filterSelectorAbout:
  119.                 if (!gdata) {
  120.                         gdata = (globals_t*)malloc(sizeof(globals_t));
  121.                         if (!gdata) break;
  122.                         gdata->standalone = gdata->parmloaded = readPARMresource((HMODULE)hDllInstance,&reason,READ_OBFUSC);
  123.                         DoAbout((AboutRecordPtr)pb);
  124.                         free(gdata);
  125.                         gdata = NULL;
  126.                 } else {
  127.                         DoAbout((AboutRecordPtr)pb);
  128.                 }
  129.                 break;
  130.         case filterSelectorParameters:
  131.                 wantdialog = true;
  132.                 break;
  133.         case filterSelectorPrepare:
  134.                 DoPrepare(pb);
  135.                 init_symtab(predefs); // ready for parser calls
  136.                 init_trigtab();
  137.                 break;
  138.         case filterSelectorStart:
  139.                 /* initialise the parameter handle that Photoshop keeps for us */
  140.                 if(!pb->parameters)
  141.                         pb->parameters = PINEWHANDLE(1); // don't set initial size to 0, since some hosts (e.g. GIMP/PSPI) are incompatible with that.
  142.  
  143.                 wantdialog |= checkandinitparams(pb->parameters);
  144.  
  145.                 /* wantdialog = false means that we never got a Parameters call, so we're not supposed to ask user */
  146.                 if( wantdialog && (!gdata->standalone || gdata->parm.popDialog) ){
  147.                         if( maindialog(pb) ){
  148.                                 if (!host_preserves_parameters()) {
  149.                                         /* Workaround for GIMP/PSPI, to avoid that formulas vanish when you re-open the main window.
  150.                                            The reason is a bug in PSPI: The host should preserve the value of pb->parameters, which PSPI does not do.
  151.                                            Also, all global variables are unloaded, so the plugin cannot preserve any data.
  152.                                            Workaround in FF 1.7: If the host GIMP is detected, then a special mode will be activated.
  153.                                            This mode saves the filter data into a temporary file "FilterFoundry.afs" and loads it
  154.                                            when the window is opened again. */
  155.                                         // Workaround: Save settings in "FilterFoundry.afs" if the host does not preserve pb->parameters
  156.                                         char outfilename[255];
  157.                                         char* tempdir;
  158.                                         StandardFileReply sfr;
  159.                                         sfr.sfGood = true;
  160.                                         sfr.sfReplacing = true;
  161.                                         sfr.sfType = PS_FILTER_FILETYPE;
  162.  
  163.                                         tempdir = getenv("TMP");
  164.                                         #ifdef WIN_ENV
  165.                                         if (strlen(tempdir) > 0) strcat(tempdir, "\\");
  166.                                         #else
  167.                                         if (strlen(tempdir) > 0) strcat(tempdir, "/");
  168.                                         #endif
  169.                                         sprintf(outfilename, "%sFilterFoundry.afs", tempdir);
  170.  
  171.                                         myc2pstrcpy(sfr.sfFile.name, outfilename);
  172.                                         #ifdef WIN_ENV
  173.                                         sfr.nFileExtension = (WORD)(strlen(outfilename) - strlen(".afs"));
  174.                                         #endif
  175.                                         sfr.sfScript = 0; // FIXME: is that ok?
  176.                                         savefile(&sfr);
  177.                                 }
  178.  
  179.                                 /* update stored parameters from new user settings */
  180.                                 saveparams(pb->parameters);
  181.                         }else
  182.                                 e = userCanceledErr;
  183.                 }
  184.                 wantdialog = false;
  185.  
  186.                 if(!e){
  187.                         if(setup(pb)){
  188.                                 DoStart(pb);
  189.                         }else{
  190.                                 SYSBEEP(1);
  191.                                 e = filterBadParameters;
  192.                         }
  193.                 }
  194.                 break;
  195.         case filterSelectorContinue:
  196.                 e = DoContinue(pb);
  197.                 break;
  198.         case filterSelectorFinish:
  199.                 DoFinish(pb);
  200.                 break;
  201.         default:
  202.                 e = filterBadParameters;
  203.         }
  204.  
  205.         *result = e;
  206.  
  207. #ifdef WIN_ENV
  208.         if (activationContextUsed) DeactivateManifest(&manifestVars);
  209. #endif
  210.  
  211.         ExitCodeResource();
  212. }
  213.  
  214. int checkandinitparams(Handle params){
  215.         char *reasonstr,*reason;
  216.         int i,bUninitializedParams;
  217.         Boolean showdialog;
  218.  
  219.         if (!host_preserves_parameters()) {
  220.                 // Workaround: Load settings in "FilterFoundry.afs" if host does not preserve pb->parameters
  221.                 char outfilename[255];
  222.                 char* tempdir;
  223.                 StandardFileReply sfr;
  224.                 sfr.sfGood = true;
  225.                 sfr.sfReplacing = true;
  226.                 sfr.sfType = PS_FILTER_FILETYPE;
  227.  
  228.                 tempdir = getenv("TMP");
  229.                 #ifdef WIN_ENV
  230.                 if (strlen(tempdir) > 0) strcat(tempdir, "\\");
  231.                 #else
  232.                 if (strlen(tempdir) > 0) strcat(tempdir, "/");
  233.                 #endif
  234.                 sprintf(outfilename, "%sFilterFoundry.afs", tempdir);
  235.  
  236.                 myc2pstrcpy(sfr.sfFile.name, outfilename);
  237.                 #ifdef WIN_ENV
  238.                 sfr.nFileExtension = (WORD)(strlen(outfilename) - strlen(".afs"));
  239.                 #endif
  240.                 sfr.sfScript = 0; // FIXME: is that ok?
  241.                 if (loadfile(&sfr, &reason)) return true;
  242.         }
  243.  
  244.         if( (bUninitializedParams = !(params && readparams(params,false,&reasonstr))) ){
  245.                 /* either the parameter handle was uninitialised,
  246.                    or the parameter data couldn't be read; set default values */
  247.  
  248.                 // see if saved parameters exist
  249.                 gdata->standalone = gdata->parmloaded = readPARMresource((HMODULE)hDllInstance,&reason,READ_OBFUSC);
  250.  
  251.  
  252.                 if(!gdata->standalone){
  253.                         // no saved settings (not standalone)
  254.                         for(i = 0; i < 8; ++i)
  255.                                 slider[i] = i*10+100;
  256.                         for(i = 0; i < 4; ++i)
  257.                                 if(expr[i])
  258.                                         free(expr[i]);
  259.                         if(gpb->imageMode == plugInModeRGBColor){
  260.                                 expr[0] = _strdup("r");
  261.                                 expr[1] = _strdup("g");
  262.                                 expr[2] = _strdup("b");
  263.                                 expr[3] = _strdup("a");
  264.                         }else{
  265.                                 expr[0] = _strdup("c");
  266.                                 expr[1] = _strdup("c");
  267.                                 expr[2] = _strdup("c");
  268.                                 expr[3] = _strdup("c");
  269.                         }
  270.                 }
  271.         }
  272.  
  273.         // let scripting system change parameters, if we're scripted;
  274.         // user may want to force display of dialog during scripting playback
  275.         switch (ReadScriptParamsOnRead()) {
  276.                 case SCR_SHOW_DIALOG:
  277.                         showdialog = true;
  278.                         break;
  279.                 case SCR_HIDE_DIALOG:
  280.                         showdialog = false;
  281.                         break;
  282.                 default:
  283.                 case SCR_NO_SCRIPT:
  284.                         showdialog = bUninitializedParams;
  285.                         break;
  286.         }
  287.  
  288.         saveparams(params);
  289.  
  290.         return showdialog;
  291. }
  292.  
  293. Boolean host_preserves_parameters() {
  294.         if (gpb->hostSig == HOSTSIG_GIMP) return false;
  295.         if (gpb->hostSig == HOSTSIG_IRFANVIEW) return false;
  296.  
  297.         /*
  298.         char x[100];
  299.         sprintf(x, "Host Signature: %u", gpb->hostSig);
  300.         simplealert(x);
  301.         */
  302.  
  303.         // We just assume the other hosts preserve the parameters
  304.         return true;
  305. }
  306.  
  307. int64_t maxspace(){
  308.         // Please see "Hosts.md" for details about the MaxSpace implementations of tested plugins
  309.  
  310.         // Plugins that don't support MaxSpace64 shall set the field to zero; then we will use MaxSpace instead.
  311.         // Also check "gpb->bufferProcs->numBufferProcs" to see if 64 bit API is available
  312.         if ((gpb->bufferProcs->numBufferProcs >= 8) && (gpb->maxSpace64 > 0)) {
  313.                 uint64_t maxSpace64 = gpb->maxSpace64;
  314.  
  315.                 return maxSpace64;
  316.         } else {
  317.                 // Note: If maxSpace gets converted from Int32 to unsigned int, we can reach up to 4 GB RAM. However, after this, there will be a wrap to 0 GB again.
  318.                 unsigned int maxSpace32 = (unsigned int) gpb->maxSpace;
  319.                 uint64_t maxSpace64 = maxSpace32;
  320.  
  321.                 if (gpb->hostSig == HOSTSIG_IRFANVIEW) maxSpace64 *= 1024; // IrfanView is giving Kilobytes instead of Bytes
  322.                 //if (gpb->hostSig == HOSTSIG_SERIF_PHOTOPLUS) maxSpace64 *= 1024; // TODO: Serif PhotoPlus also gives Kilobytes instead of bytes. But since it uses not a unique host signature, there is nothing we can do???
  323.  
  324.                 return maxSpace64;
  325.         }
  326. }
  327.  
  328. Boolean maxspace_available() {
  329.         // Please see "Hosts.md" for details about the MaxSpace implementations of tested plugins
  330.  
  331.         // GIMP PSPI sets MaxSpace to hardcoded 100 MB
  332.         if (gpb->hostSig == HOSTSIG_GIMP) return false;
  333.        
  334.         // HOSTSIG_PAINT_NET sets MaxSpace to hardcoded 1 GB, see https://github.com/0xC0000054/PSFilterPdn/issues/5
  335.         // Comment by the host author "This was done to avoid any compatibility issues with plugins handling 2 GB - 1"
  336.         if (gpb->hostSig == HOSTSIG_PAINT_NET) return false;
  337.  
  338.         return true;
  339. }
  340.  
  341. void DoPrepare(FilterRecordPtr pb){
  342.         int i;
  343.  
  344.         for(i = 4; i--;){
  345.                 if(expr[i]||tree[i]) DBG("expr[] or tree[] non-NULL in Prepare!");
  346.                 expr[i] = NULL;
  347.                 tree[i] = NULL;
  348.         }
  349.  
  350.         // Commented out by DM, 18 Dec 2018:
  351.         // This code did not work on systems with 8 GB RAM:
  352.         /*
  353.         long space = (pb->maxSpace*9)/10; // don't ask for more than 90% of available memory
  354.  
  355.         maxSpace = 512L<<10; // this is a wild guess, actually
  356.         if(maxSpace > space)
  357.                 maxSpace = space;
  358.         pb->maxSpace = maxSpace;
  359.         */
  360.  
  361.         // New variant:
  362.         if (maxspace_available()) {
  363.                 pb->maxSpace = (int32)ceil((maxspace()/10.)*9); // don't ask for more than 90% of available memory
  364.                 // FIXME: Also maxSpace64
  365.         }
  366. }
  367.  
  368. void RequestNext(FilterRecordPtr pb,long toprow){
  369.         /* Request next block of the image */
  370.  
  371.         pb->inLoPlane = pb->outLoPlane = 0;
  372.         pb->inHiPlane = pb->outHiPlane = nplanes-1;
  373.  
  374.         // if any of the formulae involve random access to image pixels,
  375.         // ask for the entire image
  376.         if(needall){
  377.                 SETRECT(pb->inRect,0,0,pb->imageSize.h,pb->imageSize.v);
  378.         }else{
  379.                 // TODO: This does not work with GIMP. So, if we are using GIMP, we should
  380.                 //       somehow always use "needall=true", and/or find out why this doesn't work
  381.                 //       with GIMP.
  382.  
  383.                 // otherwise, process the filtered area, by chunksize parts
  384.                 pb->inRect.left = pb->filterRect.left;
  385.                 pb->inRect.right = pb->filterRect.right;
  386.                 pb->inRect.top = (int16)toprow;
  387.                 pb->inRect.bottom = (int16)MIN(toprow + chunksize,pb->filterRect.bottom);
  388.  
  389.                 if(cnvused){
  390.                         // cnv() needs one extra pixel in each direction
  391.                         if(pb->inRect.left > 0)
  392.                                 --pb->inRect.left;
  393.                         if(pb->inRect.right < pb->imageSize.h)
  394.                                 ++pb->inRect.right;
  395.                         if(pb->inRect.top > 0)
  396.                                 --pb->inRect.top;
  397.                         if(pb->inRect.bottom < pb->imageSize.v)
  398.                                 ++pb->inRect.bottom;
  399.                 }
  400.         }
  401.         pb->outRect = pb->filterRect;
  402. /*
  403. {char s[0x100];sprintf(s,"RequestNext needall=%d inRect=(%d,%d,%d,%d) filterRect=(%d,%d,%d,%d)",
  404.         needall,
  405.         pb->inRect.left,pb->inRect.top,pb->inRect.right,pb->inRect.bottom,
  406.         pb->filterRect.left,pb->filterRect.top,pb->filterRect.right,pb->filterRect.bottom);dbg(s);}
  407. */
  408. }
  409.  
  410. void DoStart(FilterRecordPtr pb){
  411. //dbg("DoStart");
  412.         /* if src() or rad() functions are used, random access to the image data is required,
  413.            so we must request the entire image in a single chunk. */
  414.         chunksize = needall ? (pb->filterRect.bottom - pb->filterRect.top) : CHUNK_ROWS;
  415.         toprow = pb->filterRect.top;
  416.         RequestNext(pb,toprow);
  417. }
  418.  
  419. OSErr DoContinue(FilterRecordPtr pb){
  420.         OSErr e = noErr;
  421.         Rect fr;
  422.         long outoffset;
  423.  
  424.         if(needall)
  425.                 fr = pb->filterRect;  // filter whole selection at once
  426.         else if(cnvused){
  427.                 // we've requested one pixel extra all around
  428.                 // (see RequestNext()), just for access purposes. But filter
  429.                 // original selection only.
  430.                 fr.left = pb->filterRect.left;
  431.                 fr.right = pb->filterRect.right;
  432.                 fr.top = toprow;
  433.                 fr.bottom = MIN(toprow + chunksize,pb->filterRect.bottom);
  434.         }else  // filter whatever portion we've been given
  435.                 fr = pb->inRect;
  436.  
  437.         outoffset = (long)pb->outRowBytes*(fr.top - pb->outRect.top)
  438.                                 + (long)nplanes*(fr.left - pb->outRect.left);
  439.  
  440.         if(!(e = process_scaled(pb, true, &fr, &fr,
  441.                                 (Ptr)pb->outData+outoffset, pb->outRowBytes, 1.)))
  442.         {
  443.                 toprow += chunksize;
  444.                 if(toprow < pb->filterRect.bottom)
  445.                         RequestNext(pb,toprow);
  446.                 else{
  447.                         SETRECT(pb->inRect,0,0,0,0);
  448.                         pb->outRect = pb->maskRect = pb->inRect;
  449.                 }
  450.         }
  451.         return e;
  452. }
  453.  
  454. void DoFinish(FilterRecordPtr pb){
  455.         int i;
  456.  
  457.         WriteScriptParamsOnRead();
  458.  
  459.         for(i = 4; i--;){
  460.                 freetree(tree[i]);
  461.                 if(expr[i]) free(expr[i]);
  462.         }
  463. }
  464.