Subversion Repositories filter_foundry

Rev

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