Subversion Repositories filter_foundry

Rev

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

  1. /*
  2.     This file is part of "Filter Foundry", a filter plugin for Adobe Photoshop
  3.         Copyright (C) 2003-2009 Toby Thain, toby@telegraphics.com.au
  4.         Copyright (C) 2018-2021 Daniel Marschall, ViaThinkSoft
  5.  
  6.         This program is free software; you can redistribute it and/or modify
  7.         it under the terms of the GNU General Public License as published by
  8.         the Free Software Foundation; either version 2 of the License, or
  9.         (at your option) any later version.
  10.  
  11.         This program is distributed in the hope that it will be useful,
  12.         but WITHOUT ANY WARRANTY; without even the implied warranty of
  13.         MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  14.         GNU General Public License for more details.
  15.  
  16.         You should have received a copy of the GNU General Public License
  17.         along with this program; if not, write to the Free Software
  18.         Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  19. */
  20.  
  21. //#include <stdio.h>
  22. //#include <sound.h>
  23.  
  24. #include "ff.h"
  25.  
  26. #include "node.h"
  27. #include "funcs.h"
  28. #include "y.tab.h"
  29. #include "scripting.h"
  30. #include <math.h>
  31. #include "PIBufferSuite.h"
  32.  
  33. // GIMP (PSPI) and IrfanView preserve neither *data(gdata), nor pb->parameters between invocations!
  34. // For debugging, we can simulate it here
  35. //#define DEBUG_SIMULATE_GIMP
  36.  
  37. // Used to find out which host signatures and memory settings a plugin host has
  38. //#define SHOW_HOST_DEBUG
  39.  
  40. struct node *tree[4];
  41. char *err[4];
  42. int errpos[4],errstart[4],nplanes,cnvused,chunksize,toprow;
  43. uint8_t slider[8],map[4][0x100];
  44. value_type cell[NUM_CELLS];
  45. char *expr[4];
  46. // long maxSpace;
  47. globals_t *gdata;
  48. FilterRecordPtr gpb;
  49.  
  50. #ifdef MAC_ENV
  51.         #define HINSTANCE HANDLE
  52.         #define hDllInstance NULL /* fake this Windows-only global */
  53. #endif
  54.  
  55. #ifdef WIN_ENV
  56. #include "manifest.h"
  57. #endif
  58.  
  59. extern struct sym_rec predefs[];
  60. extern int nplanes,varused[];
  61.  
  62. int checkandinitparams(Handle params);
  63.  
  64. // MPW MrC requires prototype
  65. DLLEXPORT MACPASCAL
  66. void ENTRYPOINT(short selector,FilterRecordPtr pb,intptr_t *data,short *result);
  67.  
  68. unsigned long get_parm_hash(PARM_T *parm) {
  69.         unsigned long hash;
  70.         int i;
  71.  
  72.         hash = djb2((char*)parm->category);
  73.         hash += djb2((char*)parm->title);
  74.         hash += djb2((char*)parm->copyright);
  75.         hash += djb2((char*)parm->author);
  76.         for (i = 0; i < 4; i++) hash += hash += djb2((char*)parm->map[i]);
  77.         for (i = 0; i < 8; i++) hash += hash += djb2((char*)parm->ctl[i]);
  78.         for (i = 0; i < 4; i++) hash += hash += djb2((char*)parm->formula[i]);
  79.  
  80.         return hash;
  81. }
  82.  
  83. DLLEXPORT MACPASCAL
  84. void ENTRYPOINT(short selector, FilterRecordPtr pb, intptr_t *data, short *result){
  85.         static Boolean wantdialog = false;
  86.         static Boolean premiereWarnedOnce = false;
  87.         OSErr e = noErr;
  88.         char *reason;
  89.        
  90.         #ifdef SHOW_HOST_DEBUG
  91.         char* tmp;
  92.         #endif
  93.  
  94.         #ifdef WIN_ENV
  95.         // For Windows, we use an activation context to enforce that our Manifest resource will
  96.         // be used. This allows us to use Visual Styles, even if the host application does not
  97.         // support it.
  98.         ManifestActivationCtx manifestVars;
  99.         BOOL activationContextUsed;
  100.         #endif
  101.  
  102.         // ---------------------------------------------------------------------
  103.  
  104.         EnterCodeResource();
  105.  
  106.         #ifdef WIN_ENV
  107.         // The first 64KB of address space is always invalid
  108.         if ((intptr_t)result <= 0xffff) {
  109.                 // When the 8BF file is analyzed with VirusTotal.com, it will invoke each
  110.                 // exported function by calling
  111.                 //    C:\Windows\System32\rundll32.exe rundll32.exe FilterFoundry.8bf,PluginMain
  112.                 // But RunDLL32 requires following signature:
  113.                 //    void CALLBACK EntryPoint(HWND hwnd, HINSTANCE hinst, LPSTR lpszCmdLine, int nCmdShow);
  114.                 // Obviously, this will cause an Exception. (It crashes at *result=e because result is 0xA)
  115.                 // Here is the problem: The crash will be handled by WerFault.exe inside the
  116.                 // VirusTotal virtual machine. WerFault connects to various servers (9 DNS resolutions!) and does
  117.                 // a lot of weird things, but VirusTotal thinks that our plugin does all that stuff,
  118.                 // and so they mark our plugin as "malware"!
  119.                 // This is a problem with VirusTotal! It shall not assume that WerFault.exe actions are our actions!
  120.                 // Even processes like "MicrosoftEdgeUpdate.exe" and "SpeechRuntime.exe" are reported to be our
  121.                 // actions, although they have nothing to do with us!
  122.                 // See https://www.virustotal.com/gui/file/1f1012c567208186be455b81afc1ee407ae6476c197d633c70cc70929113223a/behavior
  123.                 //
  124.                 // TODO: Not 100% sure if the calling convention is correct...
  125.                 //       are we corrupting the stack? At least WER isn't triggered...
  126.                 return;
  127.         }
  128.         #endif
  129.        
  130.         #ifdef SHOW_HOST_DEBUG
  131.         tmp = (char*)malloc(512);
  132.         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->numBufferProcs);
  133.         simplealert(tmp);
  134.         #endif
  135.  
  136.         if (pb->hostSig == HOSTSIG_ADOBE_PREMIERE) {
  137.                 // DM 19.07.2021 : Tried running the 8BF file in Adobe Premiere 5 (yes, that's possible,
  138.                 // and there is even a FilterFactory for Premeire!),
  139.                 // but it crashes in evalpixel() where there is write-access to the "outp".
  140.                 // Probably the canvas structure is different (maybe it contains frames to achieve transitions?)
  141.                 if (!premiereWarnedOnce) {
  142.                         simplealert(_strdup("This version of Filter Foundry is not compatible with Adobe Premiere!"));
  143.                 }
  144.                 premiereWarnedOnce = true;
  145.                 *result = errPlugInHostInsufficient;
  146.                 return;
  147.         }
  148.  
  149.         #ifdef DEBUG_SIMULATE_GIMP
  150.         *data = 0;
  151.         pb->parameters = pb->handleProcs->newProc(1);
  152.         #endif
  153.  
  154.         // Register "gdata" that contains the PARM information and other things which need to be persistant
  155.         // and preserve then in *data
  156.         // TODO: memory leak? where is the stuff freed?
  157.         if (selector != filterSelectorAbout && !*data) {
  158.                 /*
  159.                 PSBufferSuite1* pSBufferSuite32 = NULL;
  160.  
  161.                 if ((pb->sSPBasic == 0) ||
  162.                         (pb->sSPBasic->AcquireSuite(kPSBufferSuite, kPSBufferSuiteVersion1, (const void**)&pSBufferSuite32)) ||
  163.                         (pSBufferSuite32 == NULL))
  164.                 {
  165.                                 // Old deprecated buffer suite
  166.                                 BufferID tempId;
  167.                                 if ((*result = pb->bufferProcs->allocateProc(sizeof(globals_t), &tempId))) return;
  168.                                 *data = (intptr_t)pb->bufferProcs->lockProc(tempId, true);
  169.                                 gdata = (globals_t*)*data;
  170.                 }
  171.                 else
  172.                 {
  173.                                 // New buffer suite (but only 32-bit version 1, because version 2 has problems with old Photoshop versions)
  174.                                 // Windows Photoshop 7 and CS 2 accepts kPSBufferSuiteVersion2, but doesn't correctly implement it:
  175.                                 // The symbols "New" and "GetSpace64" point to memory memory addresses outside the Photoshop.exe address range.
  176.                                 // (Other Photoshop versions were not tested.)
  177.                                 // 64-bit support for Windows was established in Photoshop CS 4,
  178.                                 // and PSBufferSuite2 was first documented in SDK CS 6.
  179.                                 // So, kPSBufferSuiteVersion2 probably was partically implemented as hidden "Work in progress" version
  180.                                 // before it was publicly documented.
  181.                                 // Side note:  pb->bufferSpace64/pb->maxSpace64 was documented in SDK CC 2017.
  182.                                 //             pb->bufferProcs->allocateProc64/spaceProc64 was documented in SDK CS 6.
  183.                                 unsigned32 siz = sizeof(globals_t);
  184.                                 *data = (intptr_t)pSBufferSuite32->New(&siz, siz);
  185.                                 if ((*data == 0) || (siz == 0)) {
  186.                                                 *result = errPlugInHostInsufficient; // TODO: what is the correct error code for "out of memory"?
  187.                                                 return;
  188.                                 }
  189.                                 gdata = (globals_t*)*data;
  190.                                 pb->sSPBasic->ReleaseSuite(kPSBufferSuite, kPSBufferSuiteVersion1);
  191.                 }
  192.                 gdata->standalone = gdata->parmloaded = false;
  193.                 */
  194.  
  195.                 // We have 3 options:
  196.                 // - The deprecated buffer suite (pb->bufferProcs), works fine
  197.                 // - The recommended buffer suite (kPSBufferSuite), does NOT work (causes memory corruption?) and is not available on some hosts!
  198.                 //   Either I do something wrong, or maybe it cannot be used to store data between invocations?
  199.                 // - Using malloc(), which works also fine and is more independent from the host and easier
  200.                 *data = (intptr_t)malloc(sizeof(globals_t));
  201.                 if (*data == 0) return;
  202.                 gdata = (globals_t*)*data;
  203.                 gdata->standalone = gdata->parmloaded = false; // they will be set later
  204.         }
  205.         else {
  206.                 // We have data from the previous invocation. Use it instead
  207.                 gdata = (globals_t*)*data;
  208.         }
  209.  
  210.         #ifdef WIN_ENV
  211.         activationContextUsed = ActivateManifest((HMODULE)hDllInstance, 1, &manifestVars);
  212.         #endif
  213.  
  214.         gpb = pb;
  215.  
  216.         nplanes = MIN(pb->planes,4);
  217.  
  218.         switch (selector){
  219.         case filterSelectorAbout:
  220.                 if (!gdata) {
  221.                         gdata = (globals_t*)malloc(sizeof(globals_t));
  222.                         if (!gdata) break;
  223.                         gdata->standalone = gdata->parmloaded = readPARMresource((HMODULE)hDllInstance,&reason,READ_OBFUSC);
  224.                         DoAbout((AboutRecordPtr)pb);
  225.                         free(gdata);
  226.                         gdata = NULL;
  227.                 } else {
  228.                         DoAbout((AboutRecordPtr)pb);
  229.                 }
  230.                 break;
  231.         case filterSelectorParameters:
  232.                 wantdialog = true;
  233.                 break;
  234.         case filterSelectorPrepare:
  235.                 DoPrepare(pb);
  236.                 init_symtab(predefs); // ready for parser calls
  237.                 init_trigtab();
  238.                 break;
  239.         case filterSelectorStart:
  240.                 if (HAS_BIG_DOC(pb)) {
  241.                         // The BigDocument structure is required if the document is larger than 30,000 pixels
  242.                         // It deprecates imageSize, filterRect, inRect, outRect, maskRect, floatCoord, and wholeSize.
  243.                         // By setting it to nonzero, we communicate to Photoshop that we support the BigDocument structure.
  244.                         pb->bigDocumentData->PluginUsing32BitCoordinates = true;
  245.                 }
  246.  
  247.                 /* initialise the parameter handle that Photoshop keeps for us */
  248.                 if(!pb->parameters)
  249.                         pb->parameters = PINEWHANDLE(1); // don't set initial size to 0, since some hosts (e.g. GIMP/PSPI) are incompatible with that.
  250.  
  251.                 wantdialog |= checkandinitparams(pb->parameters);
  252.  
  253.                 /* wantdialog = false means that we never got a Parameters call, so we're not supposed to ask user */
  254.                 if( wantdialog && (!gdata->standalone || gdata->parm.popDialog) ){
  255.                         if( maindialog(pb) ){
  256.                                 if (!host_preserves_parameters()) {
  257.                                         /* Workaround for GIMP/PSPI, to avoid that formulas vanish when you re-open the main window.
  258.                                            The reason is a bug in PSPI: The host should preserve the value of pb->parameters, which PSPI does not do.
  259.                                            Also, all global variables are unloaded, so the plugin cannot preserve any data.
  260.                                            Workaround in FF 1.7: If the host GIMP is detected, then a special mode will be activated.
  261.                                            This mode saves the filter data into a temporary file "FilterFoundryXX.afs" and loads it
  262.                                            when the window is opened again. */
  263.                                         // Workaround: Save settings in "FilterFoundryXX.afs" if the host does not preserve pb->parameters
  264.                                         char outfilename[255];
  265.                                         char* tempdir;
  266.                                         int hash;
  267.                                         StandardFileReply sfr;
  268.                                         char* bakexpr[4];
  269.  
  270.                                         sfr.sfGood = true;
  271.                                         sfr.sfReplacing = true;
  272.                                         sfr.sfType = PS_FILTER_FILETYPE;
  273.  
  274.                                         tempdir = getenv("TMP");
  275.                                         #ifdef WIN_ENV
  276.                                         if (strlen(tempdir) > 0) strcat(tempdir, "\\");
  277.                                         #else
  278.                                         if (strlen(tempdir) > 0) strcat(tempdir, "/");
  279.                                         #endif
  280.  
  281.                                         hash = (gdata->standalone) ? get_parm_hash(&gdata->parm) : 0;
  282.                                         sprintf(outfilename, "%sFilterFoundry%d.afs", tempdir, hash);
  283.  
  284.                                         myc2pstrcpy(sfr.sfFile.name, outfilename);
  285.                                         #ifdef WIN_ENV
  286.                                         sfr.nFileExtension = (WORD)(strlen(outfilename) - strlen(".afs") + 1);
  287.                                         #endif
  288.                                         sfr.sfScript = 0; // FIXME: is that ok?
  289.  
  290.                                         // We only want the parameters (ctl,map) in the temporary .afs file
  291.                                         // It is important to remove the expressions, otherwise they would be
  292.                                         // revealed in the temporary files.
  293.                                         bakexpr[0] = expr[0]; // moved out of the if-definition to make the compiler happy
  294.                                         bakexpr[1] = expr[1];
  295.                                         bakexpr[2] = expr[2];
  296.                                         bakexpr[3] = expr[3];
  297.                                         if (gdata->standalone) {
  298.                                                 expr[0] = _strdup("r");
  299.                                                 expr[1] = _strdup("g");
  300.                                                 expr[2] = _strdup("b");
  301.                                                 expr[3] = _strdup("a");
  302.                                         }
  303.  
  304.                                         savefile(&sfr);
  305.  
  306.                                         if (gdata->standalone) {
  307.                                                 free(expr[0]); expr[0] = bakexpr[0];
  308.                                                 free(expr[1]); expr[1] = bakexpr[1];
  309.                                                 free(expr[2]); expr[2] = bakexpr[2];
  310.                                                 free(expr[3]); expr[3] = bakexpr[3];
  311.                                         }
  312.                                 } else {
  313.                                         /* update stored parameters from new user settings */
  314.                                         saveparams(pb->parameters);
  315.                                 }
  316.                         }else
  317.                                 e = userCanceledErr;
  318.                 }
  319.                 wantdialog = false;
  320.  
  321.                 if(!e){
  322.                         if(setup(pb)){
  323.                                 DoStart(pb);
  324.                         }else{
  325.                                 SYSBEEP(1);
  326.                                 e = filterBadParameters;
  327.                         }
  328.                 }
  329.                 break;
  330.         case filterSelectorContinue:
  331.                 e = DoContinue(pb);
  332.                 break;
  333.         case filterSelectorFinish:
  334.                 DoFinish(pb);
  335.                 break;
  336.         default:
  337.                 e = filterBadParameters;
  338.         }
  339.  
  340.         *result = e;
  341.  
  342.         #ifdef WIN_ENV
  343.         if (activationContextUsed) DeactivateManifest(&manifestVars);
  344.         #endif
  345.  
  346.         ExitCodeResource();
  347. }
  348.  
  349. int checkandinitparams(Handle params){
  350.         char *reasonstr,*reason;
  351.         int i,bUninitializedParams;
  352.         Boolean showdialog;
  353.  
  354.         if (!host_preserves_parameters()) {
  355.                 // Workaround: Load settings in "FilterFoundryXX.afs" if host does not preserve pb->parameters
  356.                 char outfilename[255];
  357.                 char* tempdir;
  358.                 int hash;
  359.                 Boolean isStandalone;
  360.                 StandardFileReply sfr;
  361.                 char* bakexpr[4];
  362.  
  363.                 sfr.sfGood = true;
  364.                 sfr.sfReplacing = true;
  365.                 sfr.sfType = PS_FILTER_FILETYPE;
  366.  
  367.                 // We need to set gdata->standalone after loadfile(), but we must call readPARMresource() before loadfile()
  368.                 // Reason: readPARMresource() reads parameters from the DLL while loadfile() reads parameters from the AFS file
  369.                 // But loadfile() will reset gdata->standalone ...
  370.                 isStandalone = readPARMresource((HMODULE)hDllInstance, &reason, READ_OBFUSC);
  371.  
  372.                 tempdir = getenv("TMP");
  373.                 #ifdef WIN_ENV
  374.                 if (strlen(tempdir) > 0) strcat(tempdir, "\\");
  375.                 #else
  376.                 if (strlen(tempdir) > 0) strcat(tempdir, "/");
  377.                 #endif
  378.  
  379.                 hash = (isStandalone) ? get_parm_hash(&gdata->parm) : 0;
  380.                 sprintf(outfilename, "%sFilterFoundry%d.afs", tempdir, hash);
  381.  
  382.                 myc2pstrcpy(sfr.sfFile.name, outfilename);
  383.                 #ifdef WIN_ENV
  384.                 sfr.nFileExtension = (WORD)(strlen(outfilename) - strlen(".afs") + 1);
  385.                 #endif
  386.                 sfr.sfScript = 0; // FIXME: is that ok?
  387.  
  388.                 // We only want the parameters (ctl,map) in the temporary .afs file
  389.                 if (isStandalone) {
  390.                         bakexpr[0] = my_strdup(expr[0]);
  391.                         bakexpr[1] = my_strdup(expr[1]);
  392.                         bakexpr[2] = my_strdup(expr[2]);
  393.                         bakexpr[3] = my_strdup(expr[3]);
  394.                 }
  395.  
  396.                 if (loadfile(&sfr, &reason)) {
  397.                         gdata->standalone = gdata->parmloaded = isStandalone;
  398.  
  399.                         if (isStandalone) {
  400.                                 free(expr[0]); expr[0] = bakexpr[0];
  401.                                 free(expr[1]); expr[1] = bakexpr[1];
  402.                                 free(expr[2]); expr[2] = bakexpr[2];
  403.                                 free(expr[3]); expr[3] = bakexpr[3];
  404.                         }
  405.  
  406.                         return true;
  407.                 }
  408.         }
  409.  
  410.         if( (bUninitializedParams = !(params && readparams(params,false,&reasonstr))) ){
  411.                 /* either the parameter handle was uninitialised,
  412.                    or the parameter data couldn't be read; set default values */
  413.  
  414.                 // see if saved parameters exist
  415.                 gdata->standalone = gdata->parmloaded = readPARMresource((HMODULE)hDllInstance,&reason,READ_OBFUSC);
  416.  
  417.  
  418.                 if(!gdata->standalone){
  419.                         // no saved settings (not standalone)
  420.                         for(i = 0; i < 8; ++i)
  421.                                 slider[i] = i*10+100;
  422.                         for(i = 0; i < 4; ++i)
  423.                                 if(expr[i])
  424.                                         free(expr[i]);
  425.                         if(gpb->imageMode == plugInModeRGBColor){
  426.                                 expr[0] = _strdup("r");
  427.                                 expr[1] = _strdup("g");
  428.                                 expr[2] = _strdup("b");
  429.                                 expr[3] = _strdup("a");
  430.                         }else{
  431.                                 expr[0] = _strdup("c");
  432.                                 expr[1] = _strdup("c");
  433.                                 expr[2] = _strdup("c");
  434.                                 expr[3] = _strdup("c");
  435.                         }
  436.                 }
  437.         }
  438.  
  439.         // let scripting system change parameters, if we're scripted;
  440.         // user may want to force display of dialog during scripting playback
  441.         switch (ReadScriptParamsOnRead()) {
  442.         case SCR_SHOW_DIALOG:
  443.                 showdialog = true;
  444.                 break;
  445.         case SCR_HIDE_DIALOG:
  446.                 showdialog = false;
  447.                 break;
  448.         default:
  449.         case SCR_NO_SCRIPT:
  450.                 showdialog = bUninitializedParams;
  451.                 break;
  452.         }
  453.  
  454.         saveparams(params);
  455.  
  456.         return showdialog;
  457. }
  458.  
  459. Boolean host_preserves_parameters() {
  460.         #ifdef DEBUG_SIMULATE_GIMP
  461.         return false;
  462.         #endif
  463.  
  464.         if (gpb->hostSig == HOSTSIG_GIMP) return false;
  465.         if (gpb->hostSig == HOSTSIG_IRFANVIEW) return false;
  466.  
  467.         // We just assume the other hosts preserve the parameters
  468.         return true;
  469. }
  470.  
  471. int64_t maxspace(){
  472.         // Please see "Hosts.md" for details about the MaxSpace implementations of tested plugins
  473.  
  474.         // Plugins that don't support MaxSpace64 shall set the field to zero; then we will use MaxSpace instead.
  475.         // Also check "gpb->bufferProcs->numBufferProcs" to see if 64 bit API is available
  476.         if ((gpb->bufferProcs->numBufferProcs >= 8) && (gpb->maxSpace64 > 0)) {
  477.                 uint64_t maxSpace64 = gpb->maxSpace64;
  478.  
  479.                 return maxSpace64;
  480.         } else {
  481.                 // 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.
  482.                 unsigned int maxSpace32 = (unsigned int) gpb->maxSpace;
  483.                 uint64_t maxSpace64 = maxSpace32;
  484.  
  485.                 if (gpb->hostSig == HOSTSIG_IRFANVIEW) maxSpace64 *= 1024; // IrfanView is giving Kilobytes instead of Bytes
  486.                 //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???
  487.  
  488.                 return maxSpace64;
  489.         }
  490. }
  491.  
  492. Boolean maxspace_available() {
  493.         // Please see "Hosts.md" for details about the MaxSpace implementations of tested plugins
  494.  
  495.         // GIMP PSPI sets MaxSpace to hardcoded 100 MB
  496.         if (gpb->hostSig == HOSTSIG_GIMP) return false;
  497.  
  498.         // HOSTSIG_PAINT_NET sets MaxSpace to hardcoded 1 GB, see https://github.com/0xC0000054/PSFilterPdn/issues/5
  499.         // Comment by the host author "This was done to avoid any compatibility issues with plugins handling 2 GB - 1"
  500.         if (gpb->hostSig == HOSTSIG_PAINT_NET) return false;
  501.  
  502.         return true;
  503. }
  504.  
  505. void DoPrepare(FilterRecordPtr pb){
  506.         int i;
  507.  
  508.         for(i = 4; i--;){
  509.                 if(expr[i]||tree[i]) DBG("expr[] or tree[] non-NULL in Prepare!");
  510.                 expr[i] = NULL;
  511.                 tree[i] = NULL;
  512.         }
  513.  
  514.         // Commented out by DM, 18 Dec 2018:
  515.         // This code did not work on systems with 8 GB RAM:
  516.         /*
  517.         long space = (pb->maxSpace*9)/10; // don't ask for more than 90% of available memory
  518.  
  519.         maxSpace = 512L<<10; // this is a wild guess, actually
  520.         if(maxSpace > space)
  521.                         maxSpace = space;
  522.         pb->maxSpace = maxSpace;
  523.         */
  524.  
  525.         // New variant:
  526.         if (maxspace_available()) {
  527.                 pb->maxSpace = (int32)ceil((maxspace()/10.)*9); // don't ask for more than 90% of available memory
  528.                 // FIXME: Also maxSpace64
  529.         }
  530. }
  531.  
  532. void RequestNext(FilterRecordPtr pb,long toprow){
  533.         /* Request next block of the image */
  534.  
  535.         pb->inLoPlane = pb->outLoPlane = 0;
  536.         pb->inHiPlane = pb->outHiPlane = nplanes-1;
  537.  
  538.         if (HAS_BIG_DOC(pb)) {
  539.                 // if any of the formulae involve random access to image pixels,
  540.                 // ask for the entire image
  541.                 if (needall) {
  542.                         SETRECT(BIGDOC_IN_RECT(pb), 0, 0, BIGDOC_IMAGE_SIZE(pb).h, BIGDOC_IMAGE_SIZE(pb).v);
  543.                 } else {
  544.                         // TODO: This does not work with GIMP. So, if we are using GIMP, we should
  545.                         //       somehow always use "needall=true", and/or find out why this doesn't work
  546.                         //       with GIMP.
  547.  
  548.                         // otherwise, process the filtered area, by chunksize parts
  549.                         BIGDOC_IN_RECT(pb).left = BIGDOC_FILTER_RECT(pb).left;
  550.                         BIGDOC_IN_RECT(pb).right = BIGDOC_FILTER_RECT(pb).right;
  551.                         BIGDOC_IN_RECT(pb).top = (int32)toprow;
  552.                         BIGDOC_IN_RECT(pb).bottom = (int32)MIN(toprow + chunksize, BIGDOC_FILTER_RECT(pb).bottom);
  553.  
  554.                         if (cnvused) {
  555.                                 // cnv() needs one extra pixel in each direction
  556.                                 if (BIGDOC_IN_RECT(pb).left > 0)
  557.                                         --BIGDOC_IN_RECT(pb).left;
  558.                                 if (BIGDOC_IN_RECT(pb).right < BIGDOC_IMAGE_SIZE(pb).h)
  559.                                         ++BIGDOC_IN_RECT(pb).right;
  560.                                 if (BIGDOC_IN_RECT(pb).top > 0)
  561.                                         --BIGDOC_IN_RECT(pb).top;
  562.                                 if (BIGDOC_IN_RECT(pb).bottom < BIGDOC_IMAGE_SIZE(pb).v)
  563.                                         ++BIGDOC_IN_RECT(pb).bottom;
  564.                         }
  565.                 }
  566.                 BIGDOC_OUT_RECT(pb) = BIGDOC_FILTER_RECT(pb);
  567.                 /*
  568.                 {char s[0x100];sprintf(s,"RequestNext needall=%d inRect=(%d,%d,%d,%d) filterRect=(%d,%d,%d,%d)",
  569.                                 needall,
  570.                                 BIGDOC_IN_RECT(pb).left,BIGDOC_IN_RECT(pb).top,BIGDOC_IN_RECT(pb).right,BIGDOC_IN_RECT(pb).bottom,
  571.                                 BIGDOC_FILTER_RECT(pb).left,BIGDOC_FILTER_RECT(pb).top,BIGDOC_FILTER_RECT(pb).right,BIGDOC_FILTER_RECT(pb).bottom);dbg(s);}
  572.                 */
  573.         } else {
  574.                 // if any of the formulae involve random access to image pixels,
  575.                 // ask for the entire image
  576.                 if (needall) {
  577.                         SETRECT(IN_RECT(pb), 0, 0, IMAGE_SIZE(pb).h, IMAGE_SIZE(pb).v);
  578.                 }
  579.                 else {
  580.                         // TODO: This does not work with GIMP. So, if we are using GIMP, we should
  581.                         //       somehow always use "needall=true", and/or find out why this doesn't work
  582.                         //       with GIMP.
  583.  
  584.                         // otherwise, process the filtered area, by chunksize parts
  585.                         IN_RECT(pb).left = FILTER_RECT(pb).left;
  586.                         IN_RECT(pb).right = FILTER_RECT(pb).right;
  587.                         IN_RECT(pb).top = (int16)toprow;
  588.                         IN_RECT(pb).bottom = (int16)MIN(toprow + chunksize, FILTER_RECT(pb).bottom);
  589.  
  590.                         if (cnvused) {
  591.                                 // cnv() needs one extra pixel in each direction
  592.                                 if (IN_RECT(pb).left > 0)
  593.                                         --IN_RECT(pb).left;
  594.                                 if (IN_RECT(pb).right < IMAGE_SIZE(pb).h)
  595.                                         ++IN_RECT(pb).right;
  596.                                 if (IN_RECT(pb).top > 0)
  597.                                         --IN_RECT(pb).top;
  598.                                 if (IN_RECT(pb).bottom < IMAGE_SIZE(pb).v)
  599.                                         ++IN_RECT(pb).bottom;
  600.                         }
  601.                 }
  602.                 OUT_RECT(pb) = FILTER_RECT(pb);
  603.                 /*
  604.                 {char s[0x100];sprintf(s,"RequestNext needall=%d inRect=(%d,%d,%d,%d) filterRect=(%d,%d,%d,%d)",
  605.                                 needall,
  606.                                 IN_RECT(pb).left,IN_RECT(pb).top,IN_RECT(pb).right,IN_RECT(pb).bottom,
  607.                                 FILTER_RECT(pb).left,FILTER_RECT(pb).top,FILTER_RECT(pb).right,FILTER_RECT(pb).bottom);dbg(s);}
  608.                 */
  609.         }
  610. }
  611.  
  612. void DoStart(FilterRecordPtr pb){
  613.         /* Global variable "needall": if src() or rad() functions are used, random access to the image data is required,
  614.            so we must request the entire image in a single chunk, otherwise we will use chunksize "CHUNK_ROWS". */
  615.         if (HAS_BIG_DOC(pb)) {
  616.                 chunksize = needall ? (BIGDOC_FILTER_RECT(pb).bottom - BIGDOC_FILTER_RECT(pb).top) : CHUNK_ROWS;
  617.                 toprow = BIGDOC_FILTER_RECT(pb).top;
  618.         } else {
  619.                 chunksize = needall ? (FILTER_RECT(pb).bottom - FILTER_RECT(pb).top) : CHUNK_ROWS;
  620.                 toprow = FILTER_RECT(pb).top;
  621.         }
  622.         RequestNext(pb, toprow);
  623. }
  624.  
  625. OSErr DoContinue(FilterRecordPtr pb){
  626.         OSErr e = noErr;
  627.         long outoffset;
  628.  
  629.         if (HAS_BIG_DOC(pb)) {
  630.                 VRect fr;
  631.                 if (needall) {
  632.                         fr = BIGDOC_FILTER_RECT(pb);  // filter whole selection at once
  633.                 } else if (cnvused) {
  634.                         // we've requested one pixel extra all around
  635.                         // (see RequestNext()), just for access purposes. But filter
  636.                         // original selection only.
  637.                         fr.left = BIGDOC_FILTER_RECT(pb).left;
  638.                         fr.right = BIGDOC_FILTER_RECT(pb).right;
  639.                         fr.top = toprow;
  640.                         fr.bottom = MIN(toprow + chunksize, BIGDOC_FILTER_RECT(pb).bottom);
  641.                 } else {  // filter whatever portion we've been given
  642.                         fr = BIGDOC_IN_RECT(pb);
  643.                 }
  644.  
  645.                 outoffset = (long)pb->outRowBytes * (fr.top - BIGDOC_OUT_RECT(pb).top)
  646.                         + (long)nplanes * (fr.left - BIGDOC_OUT_RECT(pb).left);
  647.  
  648.                 if (!(e = process_scaled_bigdoc(pb, true, fr, fr,
  649.                         (Ptr)pb->outData + outoffset, pb->outRowBytes, 1.)))
  650.                 {
  651.                         toprow += chunksize;
  652.                         if (toprow < BIGDOC_FILTER_RECT(pb).bottom)
  653.                                 RequestNext(pb, toprow);
  654.                         else {
  655.                                 SETRECT(BIGDOC_IN_RECT(pb), 0, 0, 0, 0);
  656.                                 BIGDOC_OUT_RECT(pb) = BIGDOC_MASK_RECT(pb) = BIGDOC_IN_RECT(pb);
  657.                         }
  658.                 }
  659.         } else {
  660.                 Rect fr;
  661.                 if (needall) {
  662.                         fr = FILTER_RECT(pb);  // filter whole selection at once
  663.                 } else if (cnvused) {
  664.                         // we've requested one pixel extra all around
  665.                         // (see RequestNext()), just for access purposes. But filter
  666.                         // original selection only.
  667.                         fr.left = FILTER_RECT(pb).left;
  668.                         fr.right = FILTER_RECT(pb).right;
  669.                         fr.top = toprow;
  670.                         fr.bottom = MIN(toprow + chunksize, FILTER_RECT(pb).bottom);
  671.                 } else {  // filter whatever portion we've been given
  672.                         fr = IN_RECT(pb);
  673.                 }
  674.  
  675.                 outoffset = (long)pb->outRowBytes*(fr.top - OUT_RECT(pb).top)
  676.                         + (long)nplanes*(fr.left - OUT_RECT(pb).left);
  677.  
  678.                 if(!(e = process_scaled_olddoc(pb, true, fr, fr,
  679.                         (Ptr)pb->outData+outoffset, pb->outRowBytes, 1.)))
  680.                 {
  681.                         toprow += chunksize;
  682.                         if(toprow < FILTER_RECT(pb).bottom)
  683.                                 RequestNext(pb,toprow);
  684.                         else{
  685.                                 SETRECT(IN_RECT(pb),0,0,0,0);
  686.                                 OUT_RECT(pb) = MASK_RECT(pb) = IN_RECT(pb);
  687.                         }
  688.                 }
  689.         }
  690.         return e;
  691. }
  692.  
  693. void DoFinish(FilterRecordPtr pb){
  694.         int i;
  695.  
  696.         WriteScriptParamsOnRead();
  697.  
  698.         for(i = 4; i--;){
  699.                 freetree(tree[i]);
  700.                 if(expr[i]) free(expr[i]);
  701.         }
  702. }
  703.