Subversion Repositories filter_foundry

Rev

Rev 557 | 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.net
  4.     Copyright (C) 2018-2023 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 "str.h"
  27. #include "node.h"
  28. #include "funcs.h"
  29. #include "y.tab.h"
  30. #include "scripting.h"
  31. #include <math.h>
  32. #include "PIBufferSuite.h"
  33.  
  34. // GIMP (PSPI) and IrfanView preserve neither *data(gdata), nor pb->parameters between invocations!
  35. // For debugging, we can simulate it here
  36. //#define DEBUG_SIMULATE_GIMP
  37.  
  38. // Used to find out which host signatures and memory settings a plugin host has
  39. //#define SHOW_HOST_DEBUG
  40.  
  41. // Here are working variables:
  42. struct node *tree[4];
  43. TCHAR *err[4];
  44. int errpos[4],errstart[4],nplanes,cnvused,chunksize,toprow;
  45. value_type cell[NUM_CELLS];
  46.  
  47. // this is the only memory area that keeps preserved by Photoshop:
  48. globals_t *gdata;
  49. FilterRecordPtr gpb;
  50.  
  51. #ifdef MAC_ENV
  52. #define HINSTANCE HANDLE
  53. #define hDllInstance NULL /* fake this Windows-only global */
  54. #endif
  55.  
  56. #ifdef WIN_ENV
  57. #include "manifest.h"
  58. #endif
  59.  
  60. extern struct sym_rec predefs[];
  61. extern int nplanes,varused[];
  62.  
  63. int checkandinitparams(Handle params);
  64.  
  65. // MPW MrC requires prototype
  66. DLLEXPORT MACPASCAL
  67. void ENTRYPOINT(short selector,FilterRecordPtr pb,intptr_t *data,short *result);
  68.  
  69. unsigned long parm_hash(PARM_T *parm) {
  70.         unsigned long hash;
  71.         int i;
  72.  
  73.         hash = djb2(parm->szCategory);
  74.         hash += djb2(parm->szTitle);
  75.         hash += djb2(parm->szCopyright);
  76.         hash += djb2(parm->szAuthor);
  77.         for (i = 0; i < 4; i++) hash += djb2(parm->szMap[i]);
  78.         for (i = 0; i < 8; i++) hash += djb2(parm->szCtl[i]);
  79.         for (i = 0; i < 4; i++) hash += djb2(parm->szFormula[i]);
  80.  
  81.         return hash;
  82. }
  83.  
  84. size_t get_temp_afs(TCHAR* outfilename, Boolean isStandalone, PARM_T *parm) {
  85.         char* atempdir;
  86.         int hash;
  87.         size_t i, j;
  88.         TCHAR out[MAX_PATH + 1];
  89.         char ahash[20];
  90.  
  91.         // out = getenv("TMP")
  92.         atempdir = getenv("TMP");
  93.         for (i = 0; i < strlen(atempdir); i++) {
  94.                 out[i] = (TCHAR)atempdir[i];
  95.                 out[i + 1] = 0;
  96.         }
  97.  
  98.         #ifdef WIN_ENV
  99.         if (xstrlen(out) > 0) xstrcat(out, TEXT("\\"));
  100.         #else
  101.         if (xstrlen(out) > 0) xstrcat(out, TEXT("/"));
  102.         #endif
  103.  
  104.         hash = (isStandalone) ? parm_hash(parm) : 0;
  105.  
  106.         // sprintf(outfilename, "%sFilterFoundry%d.afs", atempdir, hash);
  107.         xstrcat(out, TEXT("FilterFoundry"));
  108.         _itoa(hash, &ahash[0], 10);
  109.         for (i = 0; i < strlen(ahash); i++) {
  110.                 j = xstrlen(out);
  111.                 out[j] = (TCHAR)ahash[i];
  112.                 out[j + 1] = 0;
  113.         }
  114.         xstrcat(out, TEXT(".afs"));
  115.         if (outfilename != NULL) {
  116.                 xstrcpy(outfilename, out);
  117.         }
  118.         return xstrlen(out);
  119. }
  120.  
  121. char* stristr(const char* str, const char* strSearch) {
  122.         // Source: https://stackoverflow.com/questions/27303062/strstr-function-like-that-ignores-upper-or-lower-case
  123.         char *sors, *subs, *res = NULL;
  124.         if ((sors = _strdup(str)) != NULL) {
  125.                 if ((subs = _strdup(strSearch)) != NULL) {
  126.                         res = strstr(_strlwr(sors), _strlwr(subs));
  127.                         if (res != NULL)
  128.                                 res = (char*)str + (res - sors);
  129.                         free(subs);
  130.                 }
  131.                 free(sors);
  132.         }
  133.         return res;
  134. }
  135.  
  136. #ifdef WIN_ENV
  137. BOOL CalledFromRunDLL32(HINSTANCE hinst) {
  138.         char exename[MAX_PATH];
  139.         if (GetModuleFileNameA(hinst, exename, MAX_PATH) == 0) return false;
  140.         return stristr(exename, "rundll32") != NULL;
  141. }
  142. #endif
  143.  
  144. #ifdef WIN_ENV
  145. void CALLBACK FakeRundll32(HWND hwnd, HINSTANCE hinst, LPSTR lpszCmdLine, int nCmdShow) {
  146.         UNREFERENCED_PARAMETER(hwnd);
  147.         UNREFERENCED_PARAMETER(hinst);
  148.         UNREFERENCED_PARAMETER(lpszCmdLine);
  149.         UNREFERENCED_PARAMETER(nCmdShow);
  150.  
  151.         /*
  152.         char* tmp = (char*)malloc(512);
  153.         if (tmp != 0) {
  154.                 sprintf(tmp, "hwnd: %p\nhinst: %p\nlpszCmdLine: %s\nCmdShow: %d", (void*)(hwnd), (void*)(hinst), lpszCmdLine, nCmdShow);
  155.                 MessageBoxA(0, tmp, 0, 0);
  156.                 free(tmp);
  157.         }
  158.         */
  159.  
  160.         simplealert_id(MSG_RUNDLL_ERR_ID);
  161.  
  162.         return;
  163. }
  164. #endif
  165.  
  166. void CreateDataPointer(intptr_t* data) {
  167.         // Register "gdata" that contains the PARM information and other things which need to be persistant
  168.         // and preserve them in *data
  169.         // This memory allocation is never freed, because the filter can always be invoked again.
  170.         // The memory allocation will be kept for the lifetime of the Photoshop process and
  171.         // freed together with the application termination.
  172.  
  173.         // We have at least 5 options to allocate memory:
  174.  
  175.         // (Method 1)
  176.         // 1. The deprecated Standard Buffer Suite (pb->bufferProcs) - works fine!
  177.         /*
  178.         BufferID tempId;
  179.         if (gpb->bufferProcs->allocateProc(sizeof(globals_t), &tempId) != noErr) {
  180.                 *data = NULL;
  181.         }
  182.         else {
  183.                 *data = (void*)gpb->bufferProcs->lockProc(tempId, true);
  184.                 if (*data) memset((void*)*data, 0, sizeof(globals_t));
  185.         }
  186.         */
  187.  
  188.         // (Method 2) *DOES NOT WORK*
  189.         // 2. The recommended buffer suite (kPSBufferSuite),
  190.         //    It does not work, since it causes memory corruption when the filter is invoked a second time.
  191.         //    Probably the BufferSuite cannot be used to share data between filter invocations?
  192.         //    Also, the buffer suite is only available on the Adobe Photoshop host application.
  193.         /*
  194.         FFBuffer buf;
  195.         newBuffer(&buf, sizeof(globals_t));
  196.         *data = (intptr_t)lockBuffer(&buf);
  197.         if (*data) memset((void*)*data, 0, sizeof(globals_t));
  198.         */
  199.  
  200.         // (Method 3)
  201.         // 3. Using malloc(), which works also fine and is more independent from the host. It is also easier.
  202.         //    However, we do not know how malloc() is implemented, and it might cause problems if the
  203.         //    DLL is unloaded between invocations.
  204.         /*
  205.         *data = (intptr_t)malloc(sizeof(globals_t));
  206.         if (*data) memset((void*)*data, 0, sizeof(globals_t));
  207.         */
  208.  
  209.         // (Method 4)
  210.         // 4. Using PLUGIN.DLL:NewPtr(). This does FilterFactory 3.0.4, but requires an Adobe host.
  211.         //    In Plugin.dll, the function NewPtr() and NewPtrClear() are implemented as GlobalAlloc/GlobalLock.
  212.         //    Nothing special.
  213.  
  214.         // (Method 5)
  215.         // 5. Using GlobalAlloc/GlobalLock. This does FilterFactory 3.00 (Flags GHND = GMEM_MOVEABLE and GMEM_ZEROINIT).
  216.         //    This is Windows dependant. (However, on Mac we will just call NewPtr.)
  217.         //    GlobalAlloc and LocalAlloc are equal in 32-bit windows. The memory allocation is NOT global anymore,
  218.         //    instead it is on the private heap of the application. (Therefore we do not cause a leak when
  219.         //    Photoshop is closed).
  220.         // In Filter Foundry 1.7.0.17 we define a function called NewPtrClear, which is natively supported by Mac,
  221.         // and in Windows it will be implemented using GlobalAlloc/GlobalLock.
  222.         *data = (intptr_t)NewPtrClear(sizeof(globals_t));
  223. }
  224.  
  225. DLLEXPORT MACPASCAL
  226. void ENTRYPOINT(short selector, FilterRecordPtr pb, intptr_t *data, short *result){
  227.         static Boolean wantdialog = false;
  228.         static Boolean premiereWarnedOnce = false;
  229.  
  230.         #ifdef SHOW_HOST_DEBUG
  231.         char* tmp;
  232.         #endif
  233.  
  234.         #ifdef WIN_ENV
  235.         // For Windows, we use an activation context to enforce that our Manifest resource will
  236.         // be used. This allows us to use Visual Styles, even if the host application does not
  237.         // support it.
  238.         ManifestActivationCtx manifestVars;
  239.         BOOL activationContextUsed;
  240.         #endif
  241.  
  242.         // ---------------------------------------------------------------------
  243.  
  244.         EnterCodeResource();
  245.  
  246.         #ifdef WIN_ENV
  247.         activationContextUsed = ActivateManifest((HMODULE)hDllInstance, 1, &manifestVars);
  248.         #endif
  249.  
  250.         #ifdef WIN_ENV
  251.         if ((intptr_t)result == SW_SHOWDEFAULT && CalledFromRunDLL32((HINSTANCE)pb)) {
  252.                 // When the 8BF file is analyzed with VirusTotal.com, it will invoke each
  253.                 // exported function by calling
  254.                 // loaddll64.exe 'C:\Users\user\Desktop\attachment.dll'
  255.                 //        ==>  rundll32.exe C:\Users\user\Desktop\attachment.dll,PluginMain
  256.                 //           ==> C:\Windows\system32\WerFault.exe -u -p 6612 -s 480
  257.                 //
  258.                 // But RunDLL32 requires the following signature:
  259.                 //    void __stdcall EntryPoint(HWND hwnd,      HINSTANCE hinst,    LPSTR lpszCmdLine, int nCmdShow);
  260.                 // Our signature is:
  261.                 //    void           PluginMain(short selector, FilterRecordPtr pb, intptr_t *data,    short *result);
  262.                 //
  263.                 // Obviously, this will cause an Exception. (It crashes at *result=e because result is 0xA, which is SW_SHOWDEFAULT)
  264.                 // Here is the problem: The crash will be handled by WerFault.exe inside the
  265.                 // VirusTotal virtual machine. WerFault connects to various servers (9 DNS resolutions!) and does
  266.                 // a lot of weird things, but VirusTotal thinks that our plugin does all that stuff,
  267.                 // and so they mark our plugin as "malware"!
  268.                 // This is a problem with VirusTotal! It shall not assume that WerFault.exe actions are our actions!
  269.                 // Even actions from processes like "MicrosoftEdgeUpdate.exe" and "SpeechRuntime.exe" are reported to be our
  270.                 // actions, although they have nothing to do with us!
  271.                 // See https://www.virustotal.com/gui/file/1f1012c567208186be455b81afc1ee407ae6476c197d633c70cc70929113223a/behavior
  272.                 //
  273.                 // Note in re "*result": Usually, The first 64KB of address space are always invalid. However, in Win32s (Windows 3.11), the
  274.                 // variable "result" is <=0xFFFF. So we cannot assume that result<=0xFFFF means that the call came from RunDLL32.
  275.  
  276.                 FakeRundll32((HWND)(intptr_t)selector, (HINSTANCE)pb, (LPSTR)data, (int)(intptr_t)result);
  277.                 // (I don't understand why this works! Aren't we __cdecl and rundll expected __stdcall? But why is the parameter order correct and not reversed?)
  278.  
  279.                 goto endmain;
  280.         }
  281.         #endif
  282.  
  283.         // will be changed if an error happens
  284.         *result = noErr;
  285.  
  286.         #ifdef SHOW_HOST_DEBUG
  287.         tmp = (char*)malloc(512);
  288.         sprintf(tmp, "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 == 0 ? -999/*About has no BufferProcs*/ : pb->bufferProcs->numBufferProcs);
  289.         simplealert(tmp);
  290.         free(tmp);
  291.         #endif
  292.  
  293.         if (pb->hostSig == HOSTSIG_ADOBE_PREMIERE) {
  294.                 // DM 19.07.2021 : Tried running the 8BF file in Adobe Premiere 5 + Win98
  295.                 // (yes, that's possible, and there is even a FilterFactory for Premiere!),
  296.                 // but it crashes in evalpixel() where there is write-access to the "outp".
  297.                 // DM 24.04.2022 : On Adobe Premiere 6 + Win10, the filter opens sometimes (and sometimes crashes inside DialogBoxParam),
  298.                 // but the filter is not applied when you click "OK" (because it crashes internally; only the debugger sees it)...
  299.                 // Maybe the canvas structure is different (maybe it contains frames to achieve transitions?)
  300.                 // TODO: make Filter Foundry compatible with Premiere!
  301.                 if (!premiereWarnedOnce) {
  302.                         simplealert_id(MSG_PREMIERE_COMPAT_ID);
  303.                 }
  304.                 premiereWarnedOnce = true;
  305.                 *result = errPlugInHostInsufficient;
  306.                 goto endmain;
  307.         }
  308.  
  309.         #ifdef DEBUG_SIMULATE_GIMP
  310.         *data = 0;
  311.         pb->parameters = pb->handleProcs->newProc(1);
  312.         #endif
  313.  
  314.         gpb = pb; // required for CreateDataPointer()
  315.  
  316.         if (selector != filterSelectorAbout && !*data) {
  317.                 // The filter was never called before. We allocate (zeroed) memory now.
  318.                 CreateDataPointer(data);
  319.                 if (!*data) {
  320.                         *result = memFullErr;
  321.                         goto endmain;
  322.                 }
  323.         }
  324.  
  325.         gdata = (globals_t*)*data;
  326.  
  327.         nplanes = MIN(pb->planes,4);
  328.  
  329.         switch (selector){
  330.         case filterSelectorAbout:
  331.                 if (!gdata) {
  332.                         Boolean parmReadOk;
  333.                         // This is a temporary gdata structure just for the About dialog!
  334.                         // Not to be confused with the "real" gdata during the filter invocation (containing more data).
  335.                         gdata = (globals_t*)malloc(sizeof(globals_t));
  336.                         if (!gdata) break;
  337.                         gdata->hWndMainDlg = (HWND)((PlatformData*)((AboutRecordPtr)pb)->platformData)->hwnd; // so that simplealert() works
  338.                         parmReadOk = (LOADING_OK == readPARMresource((HMODULE)hDllInstance));
  339.                         if (!parmReadOk) gdata->parm.standalone = false;
  340.                         if (parmReadOk && (gdata->parm.cbSize != PARM_SIZE) && (gdata->parm.cbSize != PARM_SIZE_PREMIERE) && (gdata->parm.cbSize != PARM_SIG_MAC)) {
  341.                                 parm_reset(true, true, true, true);
  342.                                 if (gdata->obfusc) {
  343.                                         simplealert_id(MSG_INCOMPATIBLE_OBFUSCATION_ID);
  344.                                 }
  345.                                 else {
  346.                                         simplealert_id(MSG_INVALID_PARAMETER_DATA_ID);
  347.                                 }
  348.                         }
  349.                         else {
  350.                                 DoAbout((AboutRecordPtr)pb);
  351.                         }
  352.                         free(gdata);
  353.                         gdata = NULL;
  354.                 } else {
  355.                         DoAbout((AboutRecordPtr)pb);
  356.                 }
  357.                 break;
  358.         case filterSelectorParameters:
  359.                 wantdialog = true;
  360.                 break;
  361.         case filterSelectorPrepare:
  362.                 gdata->hWndMainDlg = 0;
  363.                 DoPrepare(pb);
  364.                 init_symtab(predefs); // ready for parser calls
  365.                 init_trigtab();
  366.                 break;
  367.         case filterSelectorStart:
  368.                 if (HAS_BIG_DOC(pb)) {
  369.                         // The BigDocument structure is required if the document is larger than 30,000 pixels
  370.                         // It deprecates imageSize, filterRect, inRect, outRect, maskRect, floatCoord, and wholeSize.
  371.                         // By setting it to nonzero, we communicate to Photoshop that we support the BigDocument structure.
  372.                         pb->bigDocumentData->PluginUsing32BitCoordinates = true;
  373.                 }
  374.  
  375.                 /* initialise the parameter handle that Photoshop keeps for us */
  376.                 if(!pb->parameters)
  377.                         pb->parameters = PINEWHANDLE(1); // don't set initial size to 0, since some hosts (e.g. GIMP/PSPI) are incompatible with that.
  378.  
  379.                 if (!pb->parameters) {
  380.                         *result = memFullErr;
  381.                 }
  382.                 else
  383.                 {
  384.                         wantdialog |= checkandinitparams(pb->parameters);
  385.  
  386.                         /* wantdialog = false means that we never got a Parameters call, so we're not supposed to ask user */
  387.                         if (wantdialog && (!gdata->parm.standalone || gdata->parm.popDialog)) {
  388.                                 if (maindialog(pb)) {
  389.                                         if (!host_preserves_parameters()) {
  390.                                                 /* Workaround for GIMP/PSPI, to avoid that formulas vanish when you re-open the main window.
  391.                                                    The reason is a bug in PSPI: The host should preserve the value of pb->parameters, which PSPI does not do.
  392.                                                    Also, all global variables are unloaded, so the plugin cannot preserve any data.
  393.                                                    Workaround in FF 1.7: If the host GIMP is detected, then a special mode will be activated.
  394.                                                    This mode saves the filter data into a temporary file "FilterFoundryXX.afs" and loads it
  395.                                                    when the window is opened again. */
  396.                                                    // Workaround: Save settings in "FilterFoundryXX.afs" if the host does not preserve pb->parameters
  397.                                                 TCHAR outfilename[MAX_PATH + 1];
  398.                                                 StandardFileReply sfr;
  399.                                                 InternalState tmpState;
  400.                                                 FFSavingResult saveres;
  401.  
  402.                                                 sfr.sfGood = true;
  403.                                                 sfr.sfReplacing = true;
  404.                                                 sfr.sfType = PS_FILTER_FILETYPE;
  405.  
  406.                                                 get_temp_afs(&outfilename[0], gdata->parm.standalone, &gdata->parm);
  407.  
  408.                                                 xstrcpy(sfr.sfFile.szName, outfilename);
  409.                                                 #ifdef WIN_ENV
  410.                                                 sfr.nFileExtension = (WORD)(xstrlen(outfilename) - strlen(".afs") + 1);
  411.                                                 #endif
  412.                                                 sfr.sfScript = smSystemScript;
  413.  
  414.                                                 // We only want the parameters (ctl,map) in the temporary .afs file
  415.                                                 // It is important to remove the expressions, otherwise they would be
  416.                                                 // revealed in the temporary files (That might be bad for obfuscated filters).
  417.                                                 if (gdata->parm.standalone) {
  418.                                                         tmpState = saveInternalState();
  419.                                                         parm_reset(false, false, false, true);
  420.                                                 }
  421.  
  422.                                                 saveres = savefile_afs_pff_picotxt_guf(&sfr);
  423.  
  424.                                                 if (gdata->parm.standalone) {
  425.                                                         restoreInternalState(tmpState);
  426.                                                 }
  427.  
  428.                                                 if (saveres != SAVING_OK) {
  429.                                                         TCHAR* reason = FF_GetMsg_Cpy(saveres);
  430.                                                         alertuser_id(MSG_CANNOT_SAVE_SETTINGS_ID, reason);
  431.                                                         FF_GetMsg_Free(reason);
  432.                                                         *result = filterBadParameters;
  433.                                                 }
  434.                                         }
  435.                                         else {
  436.                                                 /* update stored parameters from new user settings */
  437.                                                 if (pb->parameters)
  438.                                                         saveparams_afs_pff(pb->parameters, false);
  439.                                         }
  440.                                 }
  441.                                 else
  442.                                         *result = userCanceledErr;
  443.                         }
  444.                         wantdialog = false;
  445.                 }
  446.  
  447.                 if(*result == noErr){
  448.                         if(setup(pb)){
  449.                                 DoStart(pb);
  450.                         }else{
  451.                                 simplealert_id(MSG_SAVED_EXPR_ERR_ID);
  452.                                 *result = filterBadParameters;
  453.                         }
  454.                 }
  455.                 break;
  456.         case filterSelectorContinue:
  457.                 *result = DoContinue(pb);
  458.                 break;
  459.         case filterSelectorFinish:
  460.                 DoFinish(pb);
  461.                 break;
  462.         default:
  463.                 *result = filterBadParameters;
  464.         }
  465.  
  466. endmain:
  467.  
  468.         // TODO: Question: Is that OK to call this every invocation, or should it be only around UI stuff?
  469.         #ifdef WIN_ENV
  470.         if (activationContextUsed) DeactivateManifest(&manifestVars);
  471.         #endif
  472.  
  473.         ExitCodeResource();
  474. }
  475.  
  476. void parm_reset(Boolean resetMetadata, Boolean resetSliderValues, Boolean resetSliderNames, Boolean resetFormulas) {
  477.         gdata->parm.cbSize = PARM_SIZE;
  478.         if (resetMetadata) {
  479.                 strcpy(gdata->parm.szCategory, "Filter Foundry");
  480.                 strcpy(gdata->parm.szTitle, "Untitled");
  481.                 strcpy(gdata->parm.szCopyright, ""); //"Filter Foundry Copyright (C) 2003-2009 Toby Thain, 2018-" RELEASE_YEAR " Daniel Marschall"
  482.                 strcpy(gdata->parm.szAuthor, "Anonymous");
  483.         }
  484.         if (resetSliderValues) {
  485.                 int i;
  486.                 for (i = 0; i < 8; ++i) {
  487.                         gdata->parm.val[i] = (uint8_t)(i * 10 + 100);
  488.                 }
  489.         }
  490.         if (resetSliderNames) {
  491.                 int i;
  492.                 for (i = 0; i < 8; ++i) {
  493.                         strcpy(gdata->parm.szCtl[i], "ctl(X)");
  494.                         gdata->parm.szCtl[i][4] = '0' + i;
  495.                 }
  496.                 for (i = 0; i < 4; ++i) {
  497.                         strcpy(gdata->parm.szMap[i], "Map X");
  498.                         gdata->parm.szMap[i][4] = '0' + i;
  499.                 }
  500.         }
  501.         if (resetFormulas) {
  502.                 if (gpb->imageMode == plugInModeRGBColor) {
  503.                         strcpy(gdata->parm.szFormula[0], "r");
  504.                         strcpy(gdata->parm.szFormula[1], "g");
  505.                         strcpy(gdata->parm.szFormula[2], "b");
  506.                         strcpy(gdata->parm.szFormula[3], "a");
  507.                 }
  508.                 else {
  509.                         strcpy(gdata->parm.szFormula[0], "c");
  510.                         strcpy(gdata->parm.szFormula[1], "c");
  511.                         strcpy(gdata->parm.szFormula[2], "c");
  512.                         strcpy(gdata->parm.szFormula[3], "c");
  513.                 }
  514.         }
  515. }
  516.  
  517. void parm_cleanup() {
  518.         // Cleanup "PARM" resource by removing stuff after the null terminators, to avoid that parts of confidential formulas are leaked
  519.         int i;
  520.  
  521.         {
  522.                 char tmp[256];
  523.  
  524.                 strcpy(tmp, gdata->parm.szCategory);
  525.                 memset(gdata->parm.szCategory, 0, sizeof(gdata->parm.szCategory));
  526.                 strcpy(gdata->parm.szCategory, tmp);
  527.  
  528.                 strcpy(tmp, gdata->parm.szTitle);
  529.                 memset(gdata->parm.szTitle, 0, sizeof(gdata->parm.szTitle));
  530.                 strcpy(gdata->parm.szTitle, tmp);
  531.  
  532.                 strcpy(tmp, gdata->parm.szCopyright);
  533.                 memset(gdata->parm.szCopyright, 0, sizeof(gdata->parm.szCopyright));
  534.                 strcpy(gdata->parm.szCopyright, tmp);
  535.  
  536.                 strcpy(tmp, gdata->parm.szAuthor);
  537.                 memset(gdata->parm.szAuthor, 0, sizeof(gdata->parm.szAuthor));
  538.                 strcpy(gdata->parm.szAuthor, tmp);
  539.         }
  540.  
  541.         for (i = 0; i < 4; i++) {
  542.                 char tmp[256];
  543.                 strcpy(tmp, gdata->parm.szMap[i]);
  544.                 memset(gdata->parm.szMap[i], 0, sizeof(gdata->parm.szMap[i]));
  545.                 strcpy(gdata->parm.szMap[i], tmp);
  546.         }
  547.  
  548.         for (i = 0; i < 8; i++) {
  549.                 char tmp[256];
  550.                 strcpy(tmp, gdata->parm.szCtl[i]);
  551.                 memset(gdata->parm.szCtl[i], 0, sizeof(gdata->parm.szCtl[i]));
  552.                 strcpy(gdata->parm.szCtl[i], tmp);
  553.         }
  554.  
  555.         for (i = 0; i < 4; i++) {
  556.                 char tmp[1024];
  557.                 strcpy(tmp, gdata->parm.szFormula[i]);
  558.                 memset(gdata->parm.szFormula[i], 0, sizeof(gdata->parm.szFormula[i]));
  559.                 strcpy(gdata->parm.szFormula[i], tmp);
  560.         }
  561. }
  562.  
  563. int checkandinitparams(Handle params){
  564.         int i;
  565.         Boolean bUninitializedParams;
  566.         Boolean showdialog;
  567.         InternalState tmpState;
  568.  
  569.         if (!host_preserves_parameters()) {
  570.                 // Workaround: Load settings in "FilterFoundryXX.afs" if host does not preserve pb->parameters
  571.                 TCHAR outfilename[MAX_PATH + 1];
  572.                 Boolean parmReadOk;
  573.                 StandardFileReply sfr;
  574.                 char bakexpr[4][MAXEXPR];
  575.  
  576.                 sfr.sfGood = true;
  577.                 sfr.sfReplacing = true;
  578.                 sfr.sfType = PS_FILTER_FILETYPE;
  579.  
  580.                 parmReadOk = (LOADING_OK == readPARMresource((HMODULE)hDllInstance));
  581.                 if (!parmReadOk) gdata->parm.standalone = false;
  582.                 if (parmReadOk && (gdata->parm.cbSize != PARM_SIZE) && (gdata->parm.cbSize != PARM_SIZE_PREMIERE) && (gdata->parm.cbSize != PARM_SIG_MAC)) {
  583.                         parm_reset(true, true, true, true);
  584.                         if (gdata->obfusc) {
  585.                                 simplealert_id(MSG_INCOMPATIBLE_OBFUSCATION_ID);
  586.                         }
  587.                         else {
  588.                                 simplealert_id(MSG_INVALID_PARAMETER_DATA_ID);
  589.                         }
  590.                         return false;
  591.                 }
  592.  
  593.                 get_temp_afs(&outfilename[0], parmReadOk, &gdata->parm);
  594.  
  595.                 xstrcpy(sfr.sfFile.szName, outfilename);
  596.                 #ifdef WIN_ENV
  597.                 sfr.nFileExtension = (WORD)(xstrlen(outfilename) - strlen(".afs") + 1);
  598.                 #endif
  599.                 sfr.sfScript = smSystemScript;
  600.  
  601.                 if (parmReadOk) {
  602.                         tmpState = saveInternalState();
  603.                 }
  604.  
  605.                 if (LOADING_OK == loadfile(&sfr)) {
  606.                         if (parmReadOk) {
  607.                                 // In the standalone filter, we only want the parameters (ctl,map) in the temporary .afs file, not the formulas
  608.                                 // We do not need to care about the metadata, because the AFS does not touch the metadata anyway
  609.                                 for (i = 0; i < 4; i++) {
  610.                                         strcpy(bakexpr[i], gdata->parm.szFormula[i]);
  611.                                 }
  612.                                 restoreInternalState(tmpState);
  613.                                 for (i = 0; i < 4; i++) {
  614.                                         strcpy(gdata->parm.szFormula[i],bakexpr[i]);
  615.                                 }
  616.                         }
  617.  
  618.                         return true;
  619.                 }
  620.         }
  621.  
  622.         if( (bUninitializedParams = !(params && (LOADING_OK == readparams_afs_pff(params, false)))) ){
  623.                 /* either the parameter handle was uninitialised,
  624.                    or the parameter data couldn't be read; set default values */
  625.  
  626.                 Boolean parmReadOk;
  627.  
  628.                 // see if saved parameters exist
  629.                 parmReadOk = (LOADING_OK == readPARMresource((HMODULE)hDllInstance));
  630.                 if (!parmReadOk) gdata->parm.standalone = false;
  631.                 if (parmReadOk && (gdata->parm.cbSize != PARM_SIZE) && (gdata->parm.cbSize != PARM_SIZE_PREMIERE) && (gdata->parm.cbSize != PARM_SIG_MAC)) {
  632.                         parm_reset(true, true, true, true);
  633.                         if (gdata->obfusc) {
  634.                                 simplealert_id(MSG_INCOMPATIBLE_OBFUSCATION_ID);
  635.                         }
  636.                         else {
  637.                                 simplealert_id(MSG_INVALID_PARAMETER_DATA_ID);
  638.                         }
  639.                         return false;
  640.                 }
  641.  
  642.                 if(!gdata->parm.standalone){
  643.                         // no saved settings (not standalone)
  644.                         parm_reset(true, true, true, true);
  645.                 }
  646.         }
  647.  
  648.         // let scripting system change parameters, if we're scripted;
  649.         // user may want to force display of dialog during scripting playback
  650.         switch (ReadScriptParamsOnRead()) {
  651.         case SCR_SHOW_DIALOG:
  652.                 showdialog = true;
  653.                 break;
  654.         case SCR_HIDE_DIALOG:
  655.                 showdialog = false;
  656.                 break;
  657.         default:
  658.         case SCR_NO_SCRIPT:
  659.                 showdialog = bUninitializedParams;
  660.                 break;
  661.         }
  662.  
  663.         if (params) saveparams_afs_pff(params, false);
  664.  
  665.         return showdialog;
  666. }
  667.  
  668. Boolean host_preserves_parameters(void) {
  669.         #ifdef DEBUG_SIMULATE_GIMP
  670.         return false;
  671.         #endif
  672.  
  673.         if (gpb->hostSig == HOSTSIG_GIMP) return false;
  674.         if (gpb->hostSig == HOSTSIG_IRFANVIEW) return false;
  675.  
  676.         // We just assume the other hosts preserve the parameters
  677.         return true;
  678. }
  679.  
  680. int64_t maxspace(void){
  681.         // Please see "Hosts.md" for details about the MaxSpace implementations of tested plugins
  682.  
  683.         // Plugins that don't support MaxSpace64 shall set the field to zero; then we will use MaxSpace instead.
  684.         // Also check "gpb->bufferProcs->numBufferProcs" to see if 64 bit API is available
  685.         if ((gpb->bufferProcs->numBufferProcs >= 8) && (gpb->maxSpace64 > 0)) {
  686.                 uint64_t maxSpace64 = gpb->maxSpace64;
  687.  
  688.                 return maxSpace64;
  689.         } else {
  690.                 // 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.
  691.                 unsigned int maxSpace32 = (unsigned int) gpb->maxSpace;
  692.                 uint64_t maxSpace64 = maxSpace32;
  693.  
  694.                 if (gpb->hostSig == HOSTSIG_IRFANVIEW) maxSpace64 *= 1024; // IrfanView is giving Kilobytes instead of Bytes
  695.                 //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???
  696.  
  697.                 return maxSpace64;
  698.         }
  699. }
  700.  
  701. Boolean maxspace_available(void) {
  702.         // Please see "Hosts.md" for details about the MaxSpace implementations of tested plugins
  703.  
  704.         // GIMP PSPI sets MaxSpace to hardcoded 100 MB
  705.         if (gpb->hostSig == HOSTSIG_GIMP) return false;
  706.  
  707.         // HOSTSIG_PAINT_NET sets MaxSpace to hardcoded 1 GB, see https://github.com/0xC0000054/PSFilterPdn/issues/5
  708.         // Comment by the host author "This was done to avoid any compatibility issues with plugins handling 2 GB - 1"
  709.         if (gpb->hostSig == HOSTSIG_PAINT_NET) return false;
  710.  
  711.         return true;
  712. }
  713.  
  714. void DoPrepare(FilterRecordPtr pb){
  715.         int i;
  716.  
  717.         for(i = 4; i--;){
  718.                 tree[i] = NULL;
  719.                 err[i] = NULL;
  720.         }
  721.  
  722.         // Commented out by DM, 18 Dec 2018:
  723.         // This code did not work on systems with 8 GB RAM:
  724.         /*
  725.         long space = (pb->maxSpace*9)/10; // don't ask for more than 90% of available memory
  726.  
  727.         maxSpace = 512L<<10; // this is a wild guess, actually
  728.         if(maxSpace > space)
  729.                         maxSpace = space;
  730.         pb->maxSpace = maxSpace;
  731.         */
  732.  
  733.         // New variant:
  734.         if (maxspace_available()) {
  735.                 // don't ask for more than 90% of available memory
  736.                 int64 ninetyPercent = (int64)ceil((maxspace() / 10.) * 9);
  737.                 if ((gpb->bufferProcs->numBufferProcs >= 8) && (gpb->maxSpace64 > 0)) {
  738.                         pb->maxSpace64 = ninetyPercent;
  739.                 }
  740.                 if (ninetyPercent <= 0x7FFFFFFF) {
  741.                         pb->maxSpace = (int32)ninetyPercent;
  742.                 }
  743.         }
  744. }
  745.  
  746. void RequestNext(FilterRecordPtr pb){
  747.         /* Request next block of the image */
  748.  
  749.         pb->inLoPlane = pb->outLoPlane = 0;
  750.         pb->inHiPlane = pb->outHiPlane = nplanes-1;
  751.  
  752.         if (HAS_BIG_DOC(pb)) {
  753.                 // if any of the formulae involve random access to image pixels,
  754.                 // ask for the entire image
  755.                 if (needall) {
  756.                         SETRECT(BIGDOC_IN_RECT(pb), 0, 0, BIGDOC_IMAGE_SIZE(pb).h, BIGDOC_IMAGE_SIZE(pb).v);
  757.                 } else {
  758.                         // TODO: This does not work with GIMP. So, if we are using GIMP, we should
  759.                         //       somehow always use "needall=true", and/or find out why this doesn't work
  760.                         //       with GIMP.
  761.  
  762.                         // otherwise, process the filtered area, by chunksize parts
  763.                         BIGDOC_IN_RECT(pb).left = BIGDOC_FILTER_RECT(pb).left;
  764.                         BIGDOC_IN_RECT(pb).right = BIGDOC_FILTER_RECT(pb).right;
  765.                         BIGDOC_IN_RECT(pb).top = (int32)toprow;
  766.                         BIGDOC_IN_RECT(pb).bottom = (int32)MIN(toprow + chunksize, BIGDOC_FILTER_RECT(pb).bottom);
  767.  
  768.                         if (cnvused) {
  769.                                 // cnv() needs one extra pixel in each direction
  770.                                 if (BIGDOC_IN_RECT(pb).left > 0)
  771.                                         --BIGDOC_IN_RECT(pb).left;
  772.                                 if (BIGDOC_IN_RECT(pb).right < BIGDOC_IMAGE_SIZE(pb).h)
  773.                                         ++BIGDOC_IN_RECT(pb).right;
  774.                                 if (BIGDOC_IN_RECT(pb).top > 0)
  775.                                         --BIGDOC_IN_RECT(pb).top;
  776.                                 if (BIGDOC_IN_RECT(pb).bottom < BIGDOC_IMAGE_SIZE(pb).v)
  777.                                         ++BIGDOC_IN_RECT(pb).bottom;
  778.                         }
  779.                 }
  780.                 BIGDOC_OUT_RECT(pb) = BIGDOC_FILTER_RECT(pb);
  781.                 /*
  782.                 {char s[0x100];sprintf(s,"RequestNext needall=%d inRect=(%d,%d,%d,%d) filterRect=(%d,%d,%d,%d)",
  783.                                 needall,
  784.                                 BIGDOC_IN_RECT(pb).left,BIGDOC_IN_RECT(pb).top,BIGDOC_IN_RECT(pb).right,BIGDOC_IN_RECT(pb).bottom,
  785.                                 BIGDOC_FILTER_RECT(pb).left,BIGDOC_FILTER_RECT(pb).top,BIGDOC_FILTER_RECT(pb).right,BIGDOC_FILTER_RECT(pb).bottom);dbg(s);}
  786.                 */
  787.         } else {
  788.                 // if any of the formulae involve random access to image pixels,
  789.                 // ask for the entire image
  790.                 if (needall) {
  791.                         SETRECT(IN_RECT(pb), 0, 0, IMAGE_SIZE(pb).h, IMAGE_SIZE(pb).v);
  792.                 }
  793.                 else {
  794.                         // TODO: This does not work with GIMP. So, if we are using GIMP, we should
  795.                         //       somehow always use "needall=true", and/or find out why this doesn't work
  796.                         //       with GIMP.
  797.  
  798.                         // otherwise, process the filtered area, by chunksize parts
  799.                         IN_RECT(pb).left = FILTER_RECT(pb).left;
  800.                         IN_RECT(pb).right = FILTER_RECT(pb).right;
  801.                         IN_RECT(pb).top = (int16)toprow;
  802.                         IN_RECT(pb).bottom = (int16)MIN(toprow + chunksize, FILTER_RECT(pb).bottom);
  803.  
  804.                         if (cnvused) {
  805.                                 // cnv() needs one extra pixel in each direction
  806.                                 if (IN_RECT(pb).left > 0)
  807.                                         --IN_RECT(pb).left;
  808.                                 if (IN_RECT(pb).right < IMAGE_SIZE(pb).h)
  809.                                         ++IN_RECT(pb).right;
  810.                                 if (IN_RECT(pb).top > 0)
  811.                                         --IN_RECT(pb).top;
  812.                                 if (IN_RECT(pb).bottom < IMAGE_SIZE(pb).v)
  813.                                         ++IN_RECT(pb).bottom;
  814.                         }
  815.                 }
  816.                 OUT_RECT(pb) = FILTER_RECT(pb);
  817.                 /*
  818.                 {char s[0x100];sprintf(s,"RequestNext needall=%d inRect=(%d,%d,%d,%d) filterRect=(%d,%d,%d,%d)",
  819.                                 needall,
  820.                                 IN_RECT(pb).left,IN_RECT(pb).top,IN_RECT(pb).right,IN_RECT(pb).bottom,
  821.                                 FILTER_RECT(pb).left,FILTER_RECT(pb).top,FILTER_RECT(pb).right,FILTER_RECT(pb).bottom);dbg(s);}
  822.                 */
  823.         }
  824. }
  825.  
  826. void DoStart(FilterRecordPtr pb){
  827.         /* Global variable "needall": if src() or rad() functions are used, random access to the image data is required,
  828.            so we must request the entire image in a single chunk, otherwise we will use chunksize "CHUNK_ROWS". */
  829.         if (HAS_BIG_DOC(pb)) {
  830.                 chunksize = needall ? (BIGDOC_FILTER_RECT(pb).bottom - BIGDOC_FILTER_RECT(pb).top) : CHUNK_ROWS;
  831.                 toprow = BIGDOC_FILTER_RECT(pb).top;
  832.         } else {
  833.                 chunksize = needall ? (FILTER_RECT(pb).bottom - FILTER_RECT(pb).top) : CHUNK_ROWS;
  834.                 toprow = FILTER_RECT(pb).top;
  835.         }
  836.         RequestNext(pb);
  837. }
  838.  
  839. OSErr DoContinue(FilterRecordPtr pb){
  840.         OSErr e = noErr;
  841.         long outoffset;
  842.  
  843.         if (HAS_BIG_DOC(pb)) {
  844.                 VRect fr;
  845.                 if (needall) {
  846.                         fr = BIGDOC_FILTER_RECT(pb);  // filter whole selection at once
  847.                 } else if (cnvused) {
  848.                         // we've requested one pixel extra all around
  849.                         // (see RequestNext()), just for access purposes. But filter
  850.                         // original selection only.
  851.                         fr.left = BIGDOC_FILTER_RECT(pb).left;
  852.                         fr.right = BIGDOC_FILTER_RECT(pb).right;
  853.                         fr.top = toprow;
  854.                         fr.bottom = MIN(toprow + chunksize, BIGDOC_FILTER_RECT(pb).bottom);
  855.                 } else {  // filter whatever portion we've been given
  856.                         fr = BIGDOC_IN_RECT(pb);
  857.                 }
  858.  
  859.                 outoffset = (long)pb->outRowBytes * (fr.top - BIGDOC_OUT_RECT(pb).top)
  860.                         + (long)nplanes * (fr.left - BIGDOC_OUT_RECT(pb).left);
  861.  
  862.                 if (!(e = process_scaled_bigdoc(pb, true, fr, fr,
  863.                         (Ptr)pb->outData + outoffset, pb->outRowBytes, 1.)))
  864.                 {
  865.                         toprow += chunksize;
  866.                         if (toprow < BIGDOC_FILTER_RECT(pb).bottom)
  867.                                 RequestNext(pb);
  868.                         else {
  869.                                 SETRECT(BIGDOC_IN_RECT(pb), 0, 0, 0, 0);
  870.                                 BIGDOC_OUT_RECT(pb) = BIGDOC_MASK_RECT(pb) = BIGDOC_IN_RECT(pb);
  871.                         }
  872.                 }
  873.         } else {
  874.                 Rect fr;
  875.                 if (needall) {
  876.                         fr = FILTER_RECT(pb);  // filter whole selection at once
  877.                 } else if (cnvused) {
  878.                         // we've requested one pixel extra all around
  879.                         // (see RequestNext()), just for access purposes. But filter
  880.                         // original selection only.
  881.                         fr.left = FILTER_RECT(pb).left;
  882.                         fr.right = FILTER_RECT(pb).right;
  883.                         fr.top = toprow;
  884.                         fr.bottom = MIN(toprow + chunksize, FILTER_RECT(pb).bottom);
  885.                 } else {  // filter whatever portion we've been given
  886.                         fr = IN_RECT(pb);
  887.                 }
  888.  
  889.                 outoffset = (long)pb->outRowBytes*(fr.top - OUT_RECT(pb).top)
  890.                         + (long)nplanes*(fr.left - OUT_RECT(pb).left);
  891.  
  892.                 if(!(e = process_scaled_olddoc(pb, true, fr, fr,
  893.                         (Ptr)pb->outData+outoffset, pb->outRowBytes, 1.)))
  894.                 {
  895.                         toprow += chunksize;
  896.                         if(toprow < FILTER_RECT(pb).bottom)
  897.                                 RequestNext(pb);
  898.                         else{
  899.                                 SETRECT(IN_RECT(pb),0,0,0,0);
  900.                                 OUT_RECT(pb) = MASK_RECT(pb) = IN_RECT(pb);
  901.                         }
  902.                 }
  903.         }
  904.         return e;
  905. }
  906.  
  907. void DoFinish(FilterRecordPtr pb){
  908.         int i;
  909.  
  910.         UNREFERENCED_PARAMETER(pb);
  911.  
  912.         WriteScriptParamsOnRead();
  913.  
  914.         for(i = 4; i--;){
  915.                 freetree(tree[i]);
  916.         }
  917. }
  918.  
  919. InternalState saveInternalState(void) {
  920.         InternalState ret;
  921.         ret.bak_obfusc = gdata->obfusc;
  922.         memcpy(&ret.bak_parm, &gdata->parm, sizeof(PARM_T));
  923.  
  924.         return ret;
  925. }
  926.  
  927. void restoreInternalState(InternalState state) {
  928.         gdata->obfusc = state.bak_obfusc;
  929.         memcpy(&gdata->parm, &state.bak_parm, sizeof(PARM_T));
  930. }
  931.