Click here to Skip to main content
Click here to Skip to main content

Hosting 8bf plugins in a .NET Application

, 3 Dec 2014 Ms-PL
Rate this:
Please Sign up or sign in to vote.
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.0, 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 pluginData in PSFilterHost.EnumerateFilters(path, true))
{
    ToolStripMenuItem child = new ToolStripMenuItem(pluginData.Title, null, RunFilter_Click);
    child.Name = pluginData.Title;
    child.Tag = pluginData;
    ToolStripMenuItem about = new ToolStripMenuItem(pluginData.Title, null, ShowAboutDialog);
    about.Tag = pluginData;
 
    if (filters.ContainsKey(pluginData.Category))
    {
        ToolStripMenuItem parent = filters[pluginData.Category];
 
        if (!parent.DropDownItems.ContainsKey(pluginData.Title))
        {
            parent.DropDownItems.Add(child);
            if (pluginData.HasAboutBox)
            {
                aboutMenuItems.Add(about);
            }
        }
    }
    else
    {
        ToolStripMenuItem parent = new ToolStripMenuItem(pluginData.Category, null, child);
        filters.Add(pluginData.Category, parent);
        if (pluginData.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, 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 it's 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;
}

Events

The UpdateProgress event allows the filter to inform the host of it's 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, progress event handler, color picker callback and other properties.
  3. If the filter is called by a "Repeat Filter" command set the ParameterData that contains the previous settings to execute the filter without showing it's dialog.
  4. Calling the RunFilter method with the PluginData of the filter you want to execute.
  5. Update the output image and save the ParameterData and other properties that the filter may have modified.
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 (repeatFilter && 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 (host.RunFilter(pluginData))
    {
        this.destinationImage = host.Dest;
 
        if (!repeatFilter)
        {
            // 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

Photoshop 2.0 Filter reference

Adobe Photoshop 3.0.4 SDK Guide

History

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 decribing 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, along with any associated source code and files, is licensed under The Microsoft Public License (Ms-PL)

Share

About the Author

0xC0000054

United States United States
No Biography provided

Comments and Discussions

 
GeneralMy vote of 5 PinprofessionalVolynsky Alex27-Aug-14 14:25 
QuestionDowload files are missing Pinmemberjlopez78826-Aug-14 6:17 
AnswerRe: Dowload files are missing Pinmember0xC000005426-Aug-14 13:24 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web03 | 2.8.141216.1 | Last Updated 3 Dec 2014
Article Copyright 2014 by 0xC0000054
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid