Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Hosting 8bf Plugins in a .NET Application

0.00/5 (No votes)
27 Dec 2015 1  
A C# library to host Photoshop-compatible filters in .NET 2.0 and later

Contents

Introduction

Many image editors and viewers allow the user to adjust the image using effect or filter plugins. One of the most common types is the 8bf filter plugin used by Adobe® Photoshop® and other software that implements the Photoshop plugin API.

This library allows applications built with .NET 2.0 and later to use 3rd party 8bf filters. It can run either 32-bit or 64-bit filters and supports processing grayscale and RGBA images in 8 or 16 bits per channel depending on the supported image types of the selected filter. For 16 bit per channel images, the WIC-based classes in .NET 3.0 and later must be used due to the lack of reliable support for 16-bit images in GDI+.

The demo application is built with .NET 4.5.2, in order for the library work with .NET 3.5 and earlier you must rebuild the PSFilterHost project with the appropriate target framework.

The latest version of this library can be downloaded using NuGet or from the project page on Codeplex.

Background

The Adobe® Photoshop® SDK allows 3rd party developers to create Import, Export, Filter, Format and other types of plugins. Although it was not intended to help developers write a plugin host, it has also been used as a guide to hosting plugins in other applications.

This library is based on version 5.0 of the Photoshop SDK version 7.0 and later have a license agreement that prevents developers of other host applications from using the information that they contain. Because of this, the new PiPL and Property suite values must be decoded using the debug output from the PiPL resource loading and Property suite calls.

Using the Code

Searching a Directory for Filters

The first step is to search a directory (and optionally any sub directories) for 8bf filters. The directories that are searched may contain both filters and shortcuts to filters in other locations.

Dictionary<string, ToolStripItem> filters = new Dictionary<string, ToolStripItem>();
List<ToolStripItem> aboutMenuItems = new List<ToolStripItem>();
 
foreach (PluginData plugin in PSFilterHost.EnumerateFilters(path, SearchOption.AllDirectories))
{
    ToolStripMenuItem child = new ToolStripMenuItem(plugin.Title, null, RunFilter_Click);
    child.Name = plugin.Title;
    child.Tag = plugin;
    ToolStripMenuItem about = new ToolStripMenuItem(plugin.Title, null, ShowAboutDialog);
    about.Tag = plugin;
 
    if (filters.ContainsKey(plugin.Category))
    {
        ToolStripMenuItem parent = filters[plugin.Category];
 
        if (!parent.DropDownItems.ContainsKey(plugin.Title))
        {
            parent.DropDownItems.Add(child);
            if (plugin.HasAboutBox)
            {
                aboutMenuItems.Add(about);
            }
        }
    }
    else
    {
        ToolStripMenuItem parent = new ToolStripMenuItem(plugin.Category, null, child);
        filters.Add(plugin.Category, parent);
        if (plugin.HasAboutBox)
        {
            aboutMenuItems.Add(about);
        }
    }
}

Disabling Filters in Unsupported Image Modes

Many filters do not support processing all of the PixelFormats that WIC can load. For example, some filters may not support Grayscale or 16-bit per channel images. The host application can disable filters which do not support processing the current PixelFormat when it loads a new image.

private void EnableFiltersForImageFormat()
{
    if (this.srcImage != null)
    {
        System.Windows.Media.PixelFormat format = this.srcImage.Format;

        ToolStripItemCollection items = this.filtersToolStripMenuItem.DropDownItems;
        for (int i = 0; i < items.Count; i++)
        {
            ToolStripMenuItem menu = (ToolStripMenuItem)items[i];

            if (menu.HasDropDownItems)
            {
                ToolStripItemCollection nodes = menu.DropDownItems;
                int nCount = nodes.Count;
                List<bool> catEnabled = new List<bool>(nCount);

                for (int j = 0; j < nCount; j++)
                {
                    PluginData data = (PluginData)nodes[j].Tag;

                    bool enabled = data.SupportsImageMode(format);
                    catEnabled.Add(enabled);
                    nodes[j].Enabled = enabled;
                }

                menu.Enabled = catEnabled.Contains(true);
            }
            else
            {
                PluginData data = (PluginData)menu.Tag;

                menu.Enabled = data.SupportsImageMode(format);
            }
        }
    }
    else
    {
        // An image has not been loaded so disable all menus.
        ToolStripItemCollection items = filtersToolStripMenuItem.DropDownItems;
        for (int i = 0; i < items.Count; i++)
        {
            items[i].Enabled = false;
        }
    }
}

Optional Callbacks, Methods, Events and Properties

Callbacks

The abort callback allows the host to signal the filter to cancel any rendering currently in progress. The filter will poll this callback during long operations and stop processing if it returns true.

private bool AbortFilterCallback()
{
    return escapePressed;
}

The color picker callback allows the host to show its own color picker in place of the Windows color dialog when a filter requests that the user choose a color. The filter may specify a prompt for the user (e.g. Please choose a color: ), if the filter does not set a prompt, this will be an empty string. The red, green and blue parameters specify the initial color that the filter wants selected in the host's color dialog.

private ColorPickerResult PickColorCallback(string prompt, byte red, byte green, byte blue)
{
    ColorPickerResult color = null;

    using (ColorPickerForm dialog = new ColorPickerForm(prompt))
    {
        dialog.SetDefaultColor(red, green, blue);

        if (dialog.ShowDialog() == System.Windows.Forms.DialogResult.OK)
        {
            color = new ColorPickerResult(dialog.UserPrimaryColor);
        }
    }

    return color;
}

Methods

The SetColorProfiles method allows the host to specify the International Color Consortium (ICC) color profiles of the document and monitor which are used to apply color correction to the preview image displayed by the filter.

this.hostColorProfiles = new HostColorManagement(documentProfile, monitorProfilePath);

Events

The UpdateProgress event allows the filter to inform the host of its rendering progress.

private void UpdateProgress(object sender, FilterProgressEventArgs e)
{
    this.toolStripProgressBar1.Value = e.Progress;
}

Properties

The HostInfo property allows the filter to retrieve information about the current document from the host, such as the title and preferred ruler measurement unit.

this.hostInfo = new HostInformation();
this.hostInfo.Title = this.imageFileName;
this.hostInfo.RulerUnit = HostRulerUnit.Inches;

The PseudoResources property allows the filter to store data which will persist for the current session, this can be used for communication between filters.

PseudoResourceCollection pseudoResources = null;

Executing a Filter

After the user selects a filter to run, it can be executed with the following steps:

  1. Calling one of the PSFilterHost constructor overloads.
  2. Optionally set the abort callback, color picker callback, progress event handler and other properties.
  3. Set the FilterParameters property to restore the last used settings (if any).
  4. Calling one of the RunFilter overloads with the PluginData of the filter you want to execute.
  5. Update the output image and save the FilterParameters and other properties that the filter may have modified.

When the FilterParameters have been set to execute a filter with settings from a previous session the RunFilter(PluginData) method will not show the user interface. The RunFilter(PluginData, Boolean) overload allows the host to display the filters user interface initialized to the previous settings. The host should display the filters user interface unless it was invoked through a 'Repeat Filter' command.

using (PSFilterHost host = new PSFilterHost(source, foreColor, backColor, selection, owner))
{
    host.SetAbortCallback(new AbortFunc(AbortFilterCallback));
    host.SetPickColorCallback(new PickColor(PickColorCallback));
    host.UpdateProgress += new EventHandler(UpdateProgress);

    if (this.filterParameters.ContainsKey(pluginData))
    {
        host.FilterParameters = this.filterParameters[pluginData];
    }
 
    if ((this.pseudoResources != null) && this.pseudoResources.Count > 0)
    {
        host.PseudoResources = this.pseudoResources;
    }
 
    host.HostInfo = this.hostInfo;
    if (this.hostColorProfiles != null)
    {
        host.SetColorProfiles(this.hostColorProfiles);
    }
 
    if (host.RunFilter(pluginData, showUI))
    {
        this.destinationImage = host.Dest;
 
        if (showUI)
        {
            // Add or update the last used parameters of the filter. 
            if (this.filterParameters.ContainsKey(pluginData))
            {
                this.filterParameters[pluginData] = host.FilterParameters;
            }
            else
            {
                this.filterParameters.Add(pluginData, host.FilterParameters);
            }
 
            // Save the other information that may have been changed by the filter.
            this.pseudoResources = host.PseudoResources;
            this.hostInfo = host.HostInfo;
        }
    }
}

Displaying the Filter About Box

The ShowAboutBox function displays the filter's about box (if it has one).

PSFilterHost.ShowAboutDialog(pluginData, parentWindowHandle);

Points of Interest

The Photoshop API uses 16-bit signed integers for the image dimensions so this library supports a maximum of 32,000 pixels in width and/or height, Photoshop 7.0 and earlier were limited to 30,000 pixels which was increased to 300,000 pixels in Photoshop CS and later.

The EXIF and XMP metadata is exposed to the filters as a pointer to the start of the TIFF container for EXIF or the start of the XML packet for XMP.

The 5.0 PICA suites are only enabled for certain filters due to the fact that many filters do not check the return code before using a suite that may not be implemented.

Since the release of .NET 3.5, the C# and VB.NET compilers enable Data Execution Prevention for 32-bit applications. As many filters are not compatible with it, you must use editbin or a similar tool to clear the IMAGE_DLLCHARACTERISTICS_NX_COMPAT flag (see NXCOMPAT and the C# compiler for more information).

References

History

12-27-2015

  • Updated source code and demo application to match the 1.4.0.0 release on Codeplex
  • Added a methods header to the Optional Callbacks, Events and Properties section
  • Updated the Executing a filter section to use the new SetColorProfiles(HostColorManagement) method

10-19-2015

  • Updated source code and demo application to match the 1.3.0.0 release on Codeplex
  • Changed the Executing a filter section to use the new RunFilter(PluginData, Boolean) overload

04-12-2015

  • Updated source code and demo application to match the 1.2.0.0 release on Codeplex
  • Changed the Searching a directory for filters example code to use the new System.IO.SearchOption overload

12-03-2014

  • Updated source code and demo application to match the 1.1.0.6 release on Codeplex

11-08-2014

  • Added a section describing how to disable filters that do not support the current image mode
  • Updated source code and demo application to match the 1.1.0.5 release on Codeplex

09-28-2014

  • Added a section describing the optional callbacks, events and properties

08-30-2014

  • Added some information about 32-bit host applications and Data Execution Prevention
  • Updated source code and demo application to match the 1.1.0.4 release on Codeplex

08-25-2014

  • Original article

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here