ACF Library

MakePlatformBannerImages

Back to List

Description: Full package for adding plattform banners overlay on product images. Uses ImageMagic

FileMaker Prototype:

Set Variable [$res; ACF_Run("MakePlatformBannerImages"; string_srcImagePath;  string_templatesFolder;  string_outFolder;  string_optionsJSON)]

Category: UTILITY

Function source:

Package ImageManipulation "Functions to manipulate images";

use bootstrap; // GetFileNameFromPath, GetExtentionFromPath

// ==============================
// Helpers (top-level functions)
// ==============================

// filename without extension
function ACF_BaseNameNoExt ( string path ) // -> string
    string name = GetFileNameFromPath(path);   // e.g. "photo.jpg"
    string ext  = GetExtentionFromPath(path);  // from bootstrap (NOTE: ext from full path)
    return substitute(name, "." + ext, "");
end

function GetDirectoriesFromPath2 ( string path ) 
   
   if (isMac) then
        path = substitute ( path, ":", "/");
   end if
   path = substitute ( path, "\\", "/");
   return regex_replace("^(.+)/(.+\..+)$", path, "\1");
end

// parent folder
function ACF_ParentDir ( string path ) // -> string
    return GetDirectoriesFromPath2(path);
end

// --- OS helpers ---


// Escaped parentheses for the current shell (needed when we use (...) in IM)
// mac(zsh/bash): \( \)   |  Windows(cmd.exe): ^( ^)
function ACF_ParenOpenEsc()  // -> string
    if ( isWindows ) then return "^("; end if
    return "\\(";
end
function ACF_ParenCloseEsc() // -> string
    if ( isWindows ) then return "^)"; end if
    return "\\)";
end

// normalize odd absolute paths on macOS
function ACF_EnsureVolPrefix ( string p ) // -> string
    if ( isMac && left(p,1)=="/" && left(p,8)!="/Volumes" && left(p,6)!="/Users" ) then
        return "/Volumes" + p;
    end if
    return p;
end

// ensure directory exists
function ACF_EnsureDir ( string dirPath ) // -> bool
    if ( directory_exists(dirPath) ) then return true; end if
    string xx = create_directory(dirPath);
    return directory_exists(dirPath);
end

// find magick binary; prefer explicit paths, then PATH resolution
function ACF_FindMagickBin () // -> string
    if ( isMac ) then
        if ( file_exists("/opt/homebrew/bin/magick") ) then return "/opt/homebrew/bin/magick"; end if
        if ( file_exists("/usr/local/bin/magick") )     then return "/usr/local/bin/magick"; end if
        if ( file_exists("/usr/local/bin/convert") )    then return "/usr/local/bin/convert"; end if
        if ( file_exists("/opt/homebrew/bin/convert") ) then return "/opt/homebrew/bin/convert"; end if
        // final fallback — rely on PATH
        return "magick";
    end if
    // Windows: ImageMagick usually adds magick.exe to PATH
    return "magick";
end

// Build an ImageMagick command for compositing with gravity/offset/resize/rotate
function ACF_BuildCompositeCmd (
    string exe, string src, string overlay, string out,
    string gravity, int dx, int dy, string resize, int rotate
) // -> string
    // Geometry offset (inset from chosen corner)
    string geo = "";
    if ( dx != 0 || dy != 0 ) then
        geo = format(" -geometry +%d+%d", dx, dy);
    end if

    // Build overlay modifiers (resize and/or rotate)
    string mods = "";
    if ( resize != "" ) then
        mods += format(" -resize %s", resize);
    end if
    if ( rotate != 0 ) then
        mods += format(" -rotate %d", rotate);
    end if

    // If we have any overlay modifiers, wrap overlay with escaped parentheses for the current shell
    string ovl;
    if ( mods != "" ) then
        string po = ACF_ParenOpenEsc();
        string pc = ACF_ParenCloseEsc();
        ovl = format(' %s "%s"%s %s ', po, overlay, mods, pc);
    else
        ovl = format(' "%s" ', overlay);
    end if

    bool useMagick = (( right(exe, 6) == "magick" ) || ( lower(GetFileNameFromPath(exe)) == "magick.exe" ));
    string exeOS = (isWindows?exe+".exe":exe); 

    if ( useMagick ) then
        // IMv7: magick <src> (<overlay mods>) -gravity ... -compose over -composite <out>
        return format('"%s" "%s"%s-gravity %s%s -compose over -composite "%s"',
                      exeOS, src, ovl, gravity, geo, out);
    end if

    // Legacy convert fallback
    return format('"%s" "%s"%s-gravity %s%s -compose over -composite "%s"',
                  exeOS, src, ovl, gravity, geo, out);
end

// Convenience: build options JSON as text
function MakeBannerOptionJSON (string resize, string dx, string dy, string gravity)
    if (dx == "") then dx = "0"; end if
    if (dy == "") then dy = "0"; end if
    if (gravity == "") then gravity = "northeast"; end if
    return string(JSON ("resize", resize, "dx", dx, "dy", dy, "gravity", gravity));
end

// ==============================
// Main
// ==============================
//
// MakePlatformBannerImages (with options)
// Params:
//   1) srcImagePath               (required)
//   2) templatesFolder            (optional) default: <src dir>/templates
//   3) outFolder                  (optional) default: <src dir>
//   4) options JSON (optional)    keys: gravity, dx, dy, resize
//
// Returns JSON: { ok, message, tool, outputs:{win,mac,macwin}, used:{gravity,dx,dy,resize}, cmdOutput:{r1,r2,r3} }
//
function MakePlatformBannerImages ( string srcImagePath, string templatesFolder, string outFolder, string optionsJSON )

    bool haveOptions = false;
    JSON optionsJS;
    if ( left(optionsJSON,1) == "{" ) then
        optionsJS    = optionsJSON;
        haveOptions  = true;
    end if

    if ( srcImagePath == "" ) then
        return JSON ( "ok", false, "message", "Missing srcImagePath" );
    end if

    // Normalize mac paths only on macOS
    if ( isMac ) then
        srcImagePath = ACF_EnsureVolPrefix(srcImagePath);
    end if

    if ( !file_exists(srcImagePath) ) then
        return JSON ( "ok", false, "message", "Source image not found", "path", srcImagePath );
    end if

    // Default folders
    if ( templatesFolder == "" ) then
        templatesFolder = ACF_ParentDir(srcImagePath) + "/templates";
    end if
    if ( isMac ) then templatesFolder = ACF_EnsureVolPrefix(templatesFolder); end if

    if ( outFolder == "" ) then
        outFolder = ACF_ParentDir(srcImagePath);
    end if
    if ( isMac ) then outFolder = ACF_EnsureVolPrefix(outFolder); end if

    if ( !ACF_EnsureDir(outFolder) ) then
        return JSON ( "ok", false, "message", "Failed to create output folder", "outFolder", outFolder );
    end if

    // Options with sane defaults
    string gravity = "northeast";
    int dx = 0;
    int dy = 0;
    string resize = "";

    if ( haveOptions ) then
        string gr  = optionsJS["gravity"];
        string sdx = optionsJS["dx"];
        string sdy = optionsJS["dy"];
        string srs = optionsJS["resize"];

        if ( gr  != "?" ) then gravity = lower(gr); end if
        if ( sdx != "?" ) then dx = int(sdx); end if
        if ( sdy != "?" ) then dy = int(sdy); end if
        if ( srs != "?" ) then resize = srs; end if
    end if

    // Clamp gravity to allowed set
    if ( pos("|northwest|northeast|southwest|southeast|center|", "|" + gravity + "|") < 0 ) then
        gravity = "northeast";
    end if

    // Template files + rotate per corner family
    string tWin, tMac, tMacWin;
    int rotate = 0;
    case
        :(gravity == "northeast")
            tWin    = templatesFolder + "/banner-win.png";
            tMac    = templatesFolder + "/banner-mac.png";
            tMacWin = templatesFolder + "/banner-macwin.png";
        :((gravity == "southwest"))
            tWin    = templatesFolder + "/sw/banner-win.png";
            tMac    = templatesFolder + "/sw/banner-mac.png";
            tMacWin = templatesFolder + "/sw/banner-macwin.png";
        :(gravity == "northwest")
            tWin    = templatesFolder + "/banner-win.png";
            tMac    = templatesFolder + "/banner-mac.png";
            tMacWin = templatesFolder + "/banner-macwin.png";
            rotate = -90;
        :((gravity == "southeast"))
            tWin    = templatesFolder + "/sw/banner-win.png";
            tMac    = templatesFolder + "/sw/banner-mac.png";
            tMacWin = templatesFolder + "/sw/banner-macwin.png";
            rotate = -90;
        default // "center" or any other, fall back to NE set
            tWin    = templatesFolder + "/banner-win.png";
            tMac    = templatesFolder + "/banner-mac.png";
            tMacWin = templatesFolder + "/banner-macwin.png";
    end case

    if ( !file_exists(tWin) || !file_exists(tMac) || !file_exists(tMacWin) ) then
        return JSON (
            "ok", false,
            "message", "Missing one or more template files (banner-win.png, banner-mac.png, banner-macwin.png)",
            "templatesFolder", templatesFolder
        );
    end if

    // Locate ImageMagick
    string magickBin = ACF_FindMagickBin();
    if ( magickBin == "" ) then
        return JSON ( "ok", false, "message", "ImageMagick not found. Install ImageMagick 7 and ensure 'magick' is on PATH." );
    end if

    // Outputs (PNG)
    string stem = ACF_BaseNameNoExt(srcImagePath);
    string outWin    = outFolder + "/" + stem + "-win.png";
    string outMac    = outFolder + "/" + stem + "-mac.png";
    string outMacWin = outFolder + "/" + stem + "-macwin.png";

    // Build commands (with options)
    string cmd1 = ACF_BuildCompositeCmd(magickBin, srcImagePath, tWin,    outWin,    gravity, dx, dy, resize, rotate);
    string cmd2 = ACF_BuildCompositeCmd(magickBin, srcImagePath, tMac,    outMac,    gravity, dx, dy, resize, rotate);
    string cmd3 = ACF_BuildCompositeCmd(magickBin, srcImagePath, tMacWin, outMacWin, gravity, dx, dy, resize, rotate);

    // Execute (echo commands for easy debugging if you capture stdout)
    string r1 = SystemCommand("echo " + cmd1 + " && " + cmd1);
    string r2 = SystemCommand("echo " + cmd2 + " && " + cmd2);
    string r3 = SystemCommand("echo " + cmd3 + " && " + cmd3);

    // Validate outputs
    if ( !file_exists(outWin) || !file_exists(outMac) || !file_exists(outMacWin) ) then
        string failMsg = "Composite failed. Verify ImageMagick install, template sizes/alpha, and write permissions.";
        return JSON (
            "ok", false,
            "message", failMsg,
            "tool", ( right(magickBin,6) == "magick" ? "magick" : "convert" ),
            "debug", cmd1 + "\n" + cmd2 + "\n" + cmd3
        );
    end if

    return JSON (
        "ok", true,
        "message", "3 images written",
        "tool", ( right(magickBin,6) == "magick" ? "magick" : "convert" ),
        "outputs", JSON(
            "win",    outWin,
            "mac",    outMac,
            "macwin", outMacWin
        ),
        "used", JSON(
            "gravity", gravity,
            "dx", dx,
            "dy", dy,
            "resize", resize
        ),
        "cmdOutput", JSON ("r1", r1, "r2", r2, "r3", r3)
    );
end

The MakePlatformBannerImages Function

Example

Set Variable [$res; ACF_Run("MakePlatformBannerImages"; string_srcImagePath;  string_templatesFolder;  string_outFolder;  string_optionsJSON)]

See the linkedin Article to explanation on use.

LinkedIn: Using ChatGPT as a Programming Assistant with the ACF Plugin and ACF Language – A Practical Example

Skjermbilde 2025-10-04 kl. 09.25.58-macwin

Back to List