ClockworkPDF Beta Release

Finally it is here. I apologize for the delay as I’ve been working on a couple other projects concurrently. This is the first public and testable version, don’t except it to be stable and production ready. I’ve tested it much as I can but if you encounter problems with certain PDF versions, please let me know. Any feedback, ideas for new features or suggestions are gladly welcome.

Oil Painting Filter in C#

Converting an image to make it look like an oil painting is not only a nice effect, but is also an easy to implement algorithm. This article demonstrates how to create an oil painting effect from an image. The oil painting filter consists of two main components: color gradients and pixel color intensities. Result images express a lesser degree of detail and tend to appear to have smaller color ranges.

Implementation

The following steps are required for the algorithm:

  1. Iterate each pixel – Every pixel forming part of the source image should be iterated. When iterating a pixel determine the neighboring pixel values based on the specified filter size (or radius).
  2. Calculate color intensityDetermine the Color Intensity of each pixel being iterated and that of the neighboring pixels. The neighboring pixels included should extend to a range determined by the Filter Size specified. The calculated value should be reduced in order to match a value ranging from zero to the number of Intensity Levels specified.
  3. Determine maximum neighborhood color intensity – When calculating the color intensities of a pixel neighborhood determine the maximum intensity value. In addition, record the occurrence of each intensity level and sum each of the Red, Green and Blue pixel color component values equating to the same intensity level.
  4. Assign the result pixel – The value assigned to the corresponding pixel in the resulting image equates to the pixel color sum total, where those pixels expressed the same intensity level. The sum total should be averaged by dividing the color sum total by the intensity level occurrence

When calculating color intensity reduced to fit the number of levels specified the formula can be expressed as follows:

colorintensityalgorithm.png

The variables in the above formula can be described as follows:

  • i – Intensity: the calculated intensity value.
  • R – Red: The value of the pixel’s red color component.
  • G – Green:
  • B – Blue:
  • l – Number of intensity levels: the maximum number of intensity levels specified.

Result Image: Filter 15, Levels 30

Oil Painting - Filter 15, Levels 30

The sample source code defines OilPaintFilter method, an extension method targeting the Bitmap class. This method determines the maximum color intensity from a pixel’s neighbors.

public static Bitmap OilPaintFilter(this Bitmap sourceBitmap, int levels, int filterSize)
        {
            Bitmap image;
            ArgbColor[] originalData;
            ArgbColor[] resultData;
            Size size;

            image = sourceBitmap;
            size = image.Size;

            originalData = image.GetPixelsFrom32BitArgbImage();
            resultData = new ArgbColor[size.Width * size.Height];

            int[] intensityBin = new int[levels];
            int[] blueBin = new int[levels];
            int[] greenBin = new int[levels];
            int[] redBin = new int[levels];

            levels = levels - 1;

            int filterOffset = (filterSize - 1) / 2;
            int byteOffset = 0;
            int calcOffset = 0;
            int currentIntensity = 0;
            int maxIntensity = 0;
            int maxIndex = 0;

            double blue = 0;
            double green = 0;
            double red = 0;

            for (int offsetY = filterOffset; offsetY < size.Height - filterOffset; offsetY++)
            {
                for (int offsetX = filterOffset; offsetX < size.Width - filterOffset; offsetX++)
                {
                    ArgbColor result;

                    blue = green = red = 0;

                    currentIntensity = maxIntensity = maxIndex = 0;

                    intensityBin = new int[levels + 1];
                    blueBin = new int[levels + 1];
                    greenBin = new int[levels + 1];
                    redBin = new int[levels + 1];

                    byteOffset = offsetY * size.Width + offsetX;

                    for (int filterY = -filterOffset; filterY <= filterOffset; filterY++)
                    {
                        for (int filterX = -filterOffset; filterX <= filterOffset; filterX++)
                        {
                            calcOffset = byteOffset + (filterX) + (filterY * size.Width);

                            currentIntensity = (int)Math.Round(((double)(originalData[calcOffset].R +
                                                originalData[calcOffset].G + originalData[calcOffset].B) / 3.0 * (levels)) / 255.0);

                            intensityBin[currentIntensity] += 1;
                            blueBin[currentIntensity] += originalData[calcOffset].B;
                            greenBin[currentIntensity] += originalData[calcOffset].G;
                            redBin[currentIntensity] += originalData[calcOffset].R;

                            if (intensityBin[currentIntensity] > maxIntensity)
                            {
                                maxIntensity = intensityBin[currentIntensity];
                                maxIndex = currentIntensity;
                            }
                        }
                    }

                    blue = blueBin[maxIndex] / maxIntensity;
                    green = greenBin[maxIndex] / maxIntensity;
                    red = redBin[maxIndex] / maxIntensity;

                    result.A = 255;
                    result.R = ClipByte(red);
                    result.G = ClipByte(green);
                    result.B = ClipByte(blue);

                    resultData[byteOffset] = result;
                }
            }

            return resultData.ToBitmap(size); ;
        }

Downloads

OilPaintingFilter.zip 13 Nov 2014 137 KB

What’s happening?

I haven’t posted anything new in a while but I’ve been very busy on the next release of RasterWave. Several new features have already been implemented including: lasso, polygon, and magic wand selection tools. Some UI layout improvements have also been made. I’m considering adding the latest development build as an additional download so users can test out new features.

Also, I have been busy working on a new program called “ClockworkPDF” which can split, merge, and rotate PDF files using batch lists with a nice UI. A beta version should be available soon as I finish up some features and debugging.

iTunes 11 Style Scroller

Spent a couple of hours seeing if I could recreate the album scroller found in the store view of the latest version of iTunes. Uses CSS3 transforms for animation and some jQuery to handle interaction. Definitely needs tidying up, and probably only works in WebKit and latest browsers for now.

Click Me

Kuwahara Filter in C#

The Kuwahara filter is probably one of those things that you haven’t heard of much, if ever. It’s a noise reduction/blurring technique for images that preserves edges, similar to a median filter. It acts like a box blur, except we take that box around the center pixel and divide that into four smaller boxes (with some overlap since the number of items in the box is usually odd along the x and y axis). These smaller boxes are calculated in the same fashion as the box blur for the average value. While we’re doing that, we’re also performing another step: Finding the variance for the box. We want to know which box has the least amount of variance. The variance can be calculated in a number of ways but the easiest is simply finding the minimum and maximum values (for r, g and b) for a box and subtracting the min from the max. The bit of code below is based on my Visual Basic source used in RasterWave:

public static Bitmap KuwaharaBlur(Bitmap Image, int Size)
{
    System.Drawing.Bitmap TempBitmap = Image;
    System.Drawing.Bitmap NewBitmap = new System.Drawing.Bitmap(TempBitmap.Width, TempBitmap.Height);
    System.Drawing.Graphics NewGraphics = System.Drawing.Graphics.FromImage(NewBitmap);
    NewGraphics.DrawImage(TempBitmap, new System.Drawing.Rectangle(0, 0, TempBitmap.Width, TempBitmap.Height), new System.Drawing.Rectangle(0, 0, TempBitmap.Width, TempBitmap.Height), System.Drawing.GraphicsUnit.Pixel);
    NewGraphics.Dispose();
    Random TempRandom = new Random();
    int[] ApetureMinX = { -(Size / 2), 0, -(Size / 2), 0 };
    int[] ApetureMaxX = { 0, (Size / 2), 0, (Size / 2) };
    int[] ApetureMinY = { -(Size / 2), -(Size / 2), 0, 0 };
    int[] ApetureMaxY = { 0, 0, (Size / 2), (Size / 2) };
    for (int x = 0; x < NewBitmap.Width; ++x)
    {
        for (int y = 0; y < NewBitmap.Height; ++y)
        {
            int[] RValues = { 0, 0, 0, 0 };
            int[] GValues = { 0, 0, 0, 0 };
            int[] BValues = { 0, 0, 0, 0 };
            int[] NumPixels = { 0, 0, 0, 0 };
            int[] MaxRValue = { 0, 0, 0, 0 };
            int[] MaxGValue = { 0, 0, 0, 0 };
            int[] MaxBValue = { 0, 0, 0, 0 };
            int[] MinRValue = { 255, 255, 255, 255 };
            int[] MinGValue = { 255, 255, 255, 255 };
            int[] MinBValue = { 255, 255, 255, 255 };
            for (int i = 0; i < 4; ++i)
            {
                for (int x2 = ApetureMinX[i]; x2 < ApetureMaxX[i]; ++x2)
                {
                    int TempX = x + x2;
                    if (TempX >= 0 && TempX < NewBitmap.Width)
                    {
                        for (int y2 = ApetureMinY[i]; y2 < ApetureMaxY[i]; ++y2)
                        {
                            int TempY = y + y2;
                            if (TempY >= 0 && TempY < NewBitmap.Height)
                            {
                                Color TempColor = TempBitmap.GetPixel(TempX, TempY);
                                RValues[i] += TempColor.R;
                                GValues[i] += TempColor.G;
                                BValues[i] += TempColor.B;
                                if (TempColor.R > MaxRValue[i])
                                {
                                    MaxRValue[i] = TempColor.R;
                                }
                                else if (TempColor.R < MinRValue[i])
                                {
                                    MinRValue[i] = TempColor.R;
                                }
 
                                if (TempColor.G > MaxGValue[i])
                                {
                                    MaxGValue[i] = TempColor.G;
                                }
                                else if (TempColor.G < MinGValue[i])
                                {
                                    MinGValue[i] = TempColor.G;
                                }
 
                                if (TempColor.B > MaxBValue[i])
                                {
                                    MaxBValue[i] = TempColor.B;
                                }
                                else if (TempColor.B < MinBValue[i])
                                {
                                    MinBValue[i] = TempColor.B;
                                }
                                ++NumPixels[i];
                            }
                        }
                    }
                }
            }
            int j = 0;
            int MinDifference = 10000;
            for (int i = 0; i < 4; ++i)
            {
                int CurrentDifference = (MaxRValue[i] - MinRValue[i]) + (MaxGValue[i] - MinGValue[i]) + (MaxBValue[i] - MinBValue[i]);
                if (CurrentDifference < MinDifference && NumPixels[i] > 0)
                {
                    j = i;
                    MinDifference = CurrentDifference;
                }
            }
 
            Color MeanPixel = Color.FromArgb(RValues[j] / NumPixels[j],
                GValues[j] / NumPixels[j],
                BValues[j] / NumPixels[j]);
            NewBitmap.SetPixel(x, y, MeanPixel);
        }
    }
    return NewBitmap;
}

The code above takes in an image as well as the size that you want the aperture to be. In turn it gives you a smoothed image. This filter technique is quite different from other blur filters as it produces a lot more stylized look. Hopefully this helps you out in some way.

Craigslist Monitor using Ruby

Overview

To add to my ever expanding knowledge base of languages, I decided to spend the last day and a half teaching myself Ruby and this was the result. A script to watch Craigslist for relevant posts so you don’t have to. My first ever Ruby program.

Watching Craigslist can be a time-consuming task. The script automates searching for housing, sales, services, jobs, and gigs, and emails you when it find something that matches your search criteria. It also works across multiple cities, since it’s controlled by the RSS feeds you configure it with.

Approach

Instead of sending Craigslist a server-side query, I decided to just get the RSS feed and filter the results on the client-side. There are two advantages of this — server changes don’t break the code and you can watch other feeds in a similar way (e.g. eBay).

Configuration

Edit the monitor.yml file to your liking. It should look something like this:

---
feeds:
  - http://sfbay.craigslist.org/wri/index.rss
  - http://sandiego.craigslist.org/wri/index.rss
include: software, developer, computer, programmer
exclude: freelance, internship, supplement, contract
email_to: someone@gmail.com
email_from:
  address: smtp.gmail.com
  port: 587
  user_name: 'a_username'
  password: 'a_password'

Running

clockwork monitor.rb

That’s it. if you want to log the output:

clockwork monitor.rb > monitor.log

Press CTRL+C to exit

Downloads

CLMonitor.zip 19 Sep 2014 3 KB

RasterWave 2.4 Release

After months of hard work, RasterWave 2.4 is officially available for download. You can grab it from the program page or use the update feature within RasterWave.

For those upgrading directly from version 2.2, you’re going to notice a lot of improvements! You can find a full write-up here, but for those in a hurry, here is an abbreviated list of what’s available in version 2.4.

  • LAYERS! RasterWave now provides comprehensive layer support, including variable opacity, custom blendmodes, on-canvas moving and resizing, and editing without flattening.
  • Numerous interface improvements, including new slider, radio button, and check box controls created specifically for RasterWave.
  • Many new tools, including straighten, auto-correction, auto-enhancement, split-toning, HDR, colored pencil, glass tiles, stained glass, fragment, Kuwahara filtering, lens flare, sunshine, noise removal (bilateral smoothing), and more.
  • Many improvements to existing tools, including overhauled Curve and Level dialogs, tonal range support when adjusting color balance, completely rewritten comic book, relief, emboss/engrave, edge enhance, find edges, rainbow, fog, and ignite filters, and many smaller tweaks and improvements.
  • New Undo History browser
  • Improved performance program-wide, including Performance options, and significant performance improvements when language translations are active.
  • Much better support for non-English locales, including many bug-fixes for locales that use “,”, as a decimal separator.
  • Improved drag/drop support, including improved support for dragging images from web browsers.
  • Improved status bar, including clickable “resize image” button and “fit image on-screen” button.
  • New Malay and Swedish language support
  • Improved support for using arrow keys to modify various settings
  • New Asynchronous image metadata processing. Images with extensive metatdata now load and save much faster than before.
  • Reduced program startup time, and greatly reduced shutdown time when many images are loaded
  • Improved mousewheel zoom, with zoom now preserving cursor location.
  • Improvements to Content-Aware resizing, including faster performance, symmetrical seam removal, and ESC-to-cancel functionality.
  • Improved image load time, especially for large images and/or complex image formats.

For a full list of this version’s bug-fixes, improvements, and enhancements, please view the What’s New file included with RasterWave.

NOTE: As you might have noticed, no screenshots are available of the program’s features. Don’t worry, I’m in the process of adding these.

Update Status

I’m attempting to upload the backlog of code I have on my harddrive as quickly as I can. I’m also running into issues moving some web applications over to the new server, which is producing some unexpected errors. Please be patient as it will take a little bit of time.

RasterWave 2.4 Beta

RasterWave 2.4 is nearing release. A few minor items still need addressing, and some language files are incomplete, but the core program is 99% ready.

The biggest change in this version is new support for image layers. As part of implementing layers, many program elements were rewritten from scratch, so this development cycle as been an arduous one. Hopefully the improvements have been worth the wait!

For users upgrading from 2.2, here is are all the new features and improvements you’ll find in version 2.4.

LAYERS

  • Each image now supports an unlimited number of layers
  • Layers can be reordered using drag-drop, or navigation buttons
  • Variable opacity and 24 blend modes are supported
  • Layered images can be resized, rotated, flipped and mirrored without flattening the image.
  • A new Layers menu provides all the same options as the Layers toolbox, plus aditional options like Flatten Image, Merge Visible Layers, adding/removing individual layer transparency, and adjusting layer orientation.
  • Layers can be moved around the canvas in real-time using the new “Move” tool.
  • The new “Move” tool also allows you to non-destructively resize layers.
  • When moving and sizing layers, you can simply click the relevant layers in the canvas without using the Layer toolbox.
  • Layered images can be now be saved in RasterWave’s own native format (.RWD, “RasterWave Document”).
  • New layer options are available in the Edit menu, including Cut and Copy From Layer, and Paste as New Layer.
  • NON-DESTRUCTIVE EDIT OPTIONS

    These live in the new “quick fix” menu on the toolbar (marked by a lightbulb). Non-destructive means that these changes are not permanently applied to the image. If you wish to undo any edit, simply set the slider back to zero, or use the “reset: button on the quick fix panel. This allows you to customize these parameters whenever you like, without harming image quality.

    NUMEROUS INTERFACE IMPROVEMENTS

    New slider, radio button, and check box controls have been created specifically for RasterWave. Besides looking prettier, they are also much more usable. The sliders in particular now provide much more feedback than the terrible old scroll bars they replaced.

    NEW TOOLS GALORE

  • New Fade tool. After applying an action to an image or layer, you can use Edit > Fade to fade the effect after the fact. Blend mode can also be adjusted.
  • New Straighen tool, for more nuanced control than Arbitrary Rotate.
  • New auto correct options for one-click color, contrast, and lighting fixes.
  • New auto-enhance options for color, contrast, and lighting. These provide an easy way to make a photo more dramatic or colorful, without fiddling with a dialog.
  • Split-toning
  • HDR adjustment
  • Colored pencil artistic effect, with four pencil modes.
  • Glass Tiles
  • Stained Glass
  • Fragment
  • Kuwahara filtering
  • Lens Flare
  • Sunshine
  • Bilateral Smoothing (noise removal)
  • IMPROVEMENTS TO EXISTING TOOLS

    Many of RasterWave’s existing tools have received performance, quality, and other improvements. Some of the notable updates include:

  • Improved Curves dialog, with new per-channel support and an improved interface (including live display of parameters).
  • Overhauled Levels dialog, including per-channel support, click-to-set color options, and a new best-in-class Auto Levels option.
  • Color Balance now allows you to limit color adjustments by tonal range.
  • Overhauled comic book filter, with new options for ink density and color smoothing.
  • Overhauled Relief filter, with new options for angle, thickness, and relief depth.
  • Overhauled Emboss/Engrave filter, with new options for angle, thickness, and depth.
  • Overhauled Edge Enhance filter, with new options for edge detection mode, direction, and strength.
  • Overhauled Find Edges dialog, with new edge operators and directionality support.
  • Overhauled Rainbow filter, with new options for offset, angle, strength, and saturation boosting.
  • Overhauled Fog filter, with new options for scale, contrast, density, and render quality.
  • Overhauled Ignite filter (formerly Burn), with new options for intensity, flame height, and strength.
  • UNDO HISTORY BROWSER

    The new Edit > Undo History browser allows you to jump to any point in an image’s past (or future, if you’ve already undone some changes).

    NEW PERFORMANCE OPTIONS

    A new Performance Options category allows you to customize how RasterWave handles certain trade-offs — for example, if you’re low on disk space, you might prefer to heavily compress Undo/Redo data, which saves space at a slight performance trade-off. Similiary, if you have an underpowered processor, you may wish to reduce the amount of on-screen effects (drop shadows, etc) in order to improve render performance.

    MINOR IMPROVEMENTS WORTH NOTING

  • Much better drag/drop support, including improved support for dragging images from web browsers.
  • Improved status bar, including a clickable “resize image” button and “fit image on-screen” button.
  • Improved support for using arrow keys to modify various settings.
  • New asynchronous image metadata processing. Images with extensive metadata now load and save much faster than before.
  • Reduced program startup time, and greatly reduced shutdown time if many images were loaded.
  • Improved mousewheel zoom, with zoom now preserving cursor location.
  • New Select > Erase selected area option.
  • Many improvements to Content-Aware resizing, including faster performance, symmetrical seam removal, and ESC-to-cancel functionality.
  • Improved image load time, especially for large images and/or complex formats.
  • OFFICIAL RELEASE TIMELINE

    Barring any major bugs, the official 2.4 release should happen within the next week or so. Automatic update notifications for existing installs will go live at that point.

    Writing Adobe Color Swatch (.aco) files using C#

    Getting started

    The previous article described how to read files in Adobe’s Swatch File format as used by Photoshop and other high-end photo editors. In this accompanying article, I’ll describe how to write such files. I’m not going to go over the structure again, so you haven’t already done so, please read the previous article for full details on the file structure and how to read it.

    Writing big-endian values

    All the data in an aco file is stored in big-endian format and therefore needs to be reversed on Windows systems before writing it back into the file.

    We can use the following two methods to write a short or an int respectively into a stream as a series of bytes. Of course, if you just want functions to convert these into bytes you could use similar code, just remove the bit-shift.

    private void WriteInt16(Stream stream, short value)
    {
      stream.WriteByte((byte)(value >> 8));
      stream.WriteByte((byte)(value >> 0));
    }
     
    private void WriteInt32(Stream stream, int value)
    {
      stream.WriteByte((byte)((value & 0xFF000000) >> 24));
      stream.WriteByte((byte)((value & 0x00FF0000) >> 16));
      stream.WriteByte((byte)((value & 0x0000FF00) >> 8));
      stream.WriteByte((byte)((value & 0x000000FF) >> 0));
    }
    

    As with the equivalent read functions, the >> 0 shift is unnecessary but it does clarify the code.

    We also need to store color swatch names, so again we’ll make use of the Encoding.BigEndianUnicode property to convert a string into a series of bytes to write out.

    private void WriteString(Stream stream, string value)
    {
      stream.Write(Encoding.BigEndianUnicode.GetBytes(value), 0, value.Length * 2);
    }
    

    Writing the file

    When writing the file, I’m going to follow the specification’s suggestion of writing a version 1 palette (for backwards compatibility), followed by a version 2 palette (for applications that support swatch names).

    using (Stream stream = File.Create(fileName))
    {
      this.WritePalette(stream, palette, FileVersion.Version1, ColorSpace.Rgb);
      this.WritePalette(stream, palette, FileVersion.Version2, ColorSpace.Rgb);
    }
    

    The core save routine follows. First, we write the version of format and then the number of colors in the palette.

    private void WritePalette(Stream stream, ICollection<Color> palette, FileVersion version, ColorSpace colorSpace)
    {
      int swatchIndex;
     
      this.WriteInt16(stream, (short)version);
      this.WriteInt16(stream, (short)palette.Count);
     
      swatchIndex = 0;
    

    With that done, we loop through each color, calculate the four values that comprise the color data and then write that.

    If it’s a version 2 file, we also write the swatch name. As these basic examples are just using the Color class, there’s no real flexibility in names, so we cheat – if it’s a “named” color, then we use the Color.Name property. Otherwise, we generate a Swatch name.

      foreach (Color color in palette)
      {
        short value1;
        short value2;
        short value3;
        short value4;
     
        swatchIndex++;
     
        switch (colorSpace)
        {
          // Calculate color space values here!
          default:
            throw new InvalidOperationException("Color space not supported.");
        }
     
        this.WriteInt16(stream, (short)colorSpace);
        this.WriteInt16(stream, value1);
        this.WriteInt16(stream, value2);
        this.WriteInt16(stream, value3);
        this.WriteInt16(stream, value4);
     
        if (version == FileVersion.Version2)
        {
          string name;
     
          name = color.IsNamedColor ? color.Name : string.Format("Swatch {0}", swatchIndex);
     
          this.WriteInt32(stream, name.Length);
          this.WriteString(stream, name);
        }
      }
    }
    

    Converting color spaces

    As previously mentioned, the specification states that each color is comprised of four values. Even if a particular color space doesn’t use all four (for example Grayscale just uses one), you still need to write the other values, typically as zero’s.

    Although it’s a slight duplication, I’ll include the description table for color spaces to allow easy reference of the value types.

    Id Description
    0 RGB. The first three values in the color data are red, green, and blue. They are full unsigned 16-bit values as in Apple’s RGBColordata structure. Pure red = 65535, 0, 0.
    7 Lab. The first three values in the color data are lightness, a chrominance, and b chrominance. Lightness is a 16-bit value from 0…10000. Chrominance components are each 16-bit values from -12800…12700. Gray values are represented by chrominance components of 0. Pure white = 10000,0,0.
    1 HSB. The first three values in the color data are hue, saturation, and brightness. They are full unsigned 16-bit values as in Apple’s HSVColordata structure. Pure red = 0,65535, 65535.
    8 Grayscale. The first value in the color data is the gray value, from 0…10000.
    2 CMYK. The four values in the color data are cyan, magenta, yellow, and black. They are full unsigned 16-bit values. For example, pure cyan = 0,65535,65535,65535.

    While supporting CMYK colors are beyond the scope of this article as they require color profiles, we can easily support RGB, HSL and Grayscale spaces.

    RGB is the simplest as .NET colors are already in this format. The only thing we have to do is divide each channel by 255 as the specification uses the range 0-65535 rather than the typical 0-255.

    Notice value4 is simply initialized to zero as this space only needs 3 of the 4 values.

    case ColorSpace.Rgb:
      value1 = (short)(color.R * 256);
      value2 = (short)(color.G * 256);
      value3 = (short)(color.B * 256);
      value4 = 0;
      break;
    

    We can also support HSL without too much trouble as the Color class already includes methods for extracting these values. Again, we need to do a little fiddling to change the number into the range used by the specification.

    case ColorSpace.Hsb:
      value1 = (short)(color.GetHue() * 182.04);
      value2 = (short)(color.GetSaturation() * 655.35);
      value3 = (short)(color.GetBrightness() * 655.35);
      value4 = 0;
      break;
    

    The last format we can easily support is grayscale. If the source color is already gray (i.e. the red, green and blue channels are all the same value), then we use that, otherwise we’ll average the 3 channels and use that as the value.

    case ColorSpace.Grayscale:
      if (color.R == color.G &amp;&amp; color.R == color.B)
      {
        // already grayscale
        value1 = (short)(color.R * 39.0625);
      }
      else
      {
        // color is not grayscale, convert
        value1 = (short)(((color.R + color.G + color.B) / 3.0) * 39.0625);
      }
      value2 = 0;
      value3 = 0;
      value4 = 0;
      break;
    

    Demo Application

    The sample generates a random 255 color palette, then writes this to a temporary file using the specified color space. It then reads it back in, and displays both palettes side by side for comparison.

    Downloads

    PhotoshopColorSwatchWriter.zip 21 July 2014 78.7 KB