Demonstration of reading a classic CorelDRAW! 3.0 palette
Demonstration of reading a classic CorelDRAW! 3.0 palette

I recently picked up a copy of CorelDRAW! 3.0 from eBay which came on two CD's with a different version on each. That gave me two different surprises, the first in that 3.0A wasn't an improved version of 3.0, and secondly instead of the .cpl format I was expecting to find, there were two different .pal formats, one text based (for CorelDRAW!) and one binary (for PHOTO-PAINT! (very shouty this software!)). This first article covers reading the text based palette format.

Unlike NCC-1701-A, 3.0A is a beta version of CorelDRAW and also has a nicer disc
Unlike NCC-1701-A, 3.0A is a beta version of CorelDRAW and also has a nicer disc


The palette format itself is simple enough, from the example colours below we can infer that each colour entry is in CMYK format with the range 0-100. Although it looks as if it is a fixed width format, when looking at other palettes using this format this isn't the case and columns can be of differing widths.

I'm not sure what limits there are on the number of colours, but one of the palettes I looked at had over 2000 colours so clearly not as limited as some older formats.

"Black"                         0   0   0 100
"90% Black"                     0   0   0  90
"White"                         0   0   0   0
"Blue"                        100 100   0   0
"Green"                       100   0 100   0
"Red"                           0 100 100   0

In order to have a common base to compare to, I recreated DawnBringer's 32 colour palette using the CorelDRAW application.

When using this application, I remembered that right click menus and tooltips were amazing inventions
When using this application, I remembered that right click menus and tooltips were amazing inventions

From experimenting with CorelDRAW, I also found that it won't allow swatch names to be longer than 29 characters.

Reading the palette

Reading the palette is straight forward enough, although one of the palettes I tested did have a old school wild card. As this is a plain text format with each colour on a single line, I'm just going to use a StreamReader to pull out each line.

public void Load(Stream stream)

  using (TextReader reader = new StreamReader(stream, Encoding.ASCII))
    string line;

    while ((line = reader.ReadLine()) != null && (line.Length == 0 || line[0] != (char)26))
      if (!string.IsNullOrEmpty(line))
        // Parse the color

End of file

In the above while loop, instead of just checking that the line is not null as I usually would, I also have an extra check to see if the first character is (char)26 and if so, to cancel loading. But what is this character?

This is the SUB or substitute character and was originally designed to be used in place of a character that is invalid. Historical DOS systems and its predecessors used this as an end of file character, which is how it has been used in one of the palettes that shipped with this version of CorelDRAW.

An interesting facet of computing history I wasn't expecting to find!

Reading the swatch name

As swatch names start with a quote and end with a quote, and are the only quoted string in the line we can find the end of the name by using LastIndexOf.

string name;

lastQuote = line.LastIndexOf('"');

name = line.Substring(1, lastQuote - 1);

As the .NET Color structure doesn't allow the Name property to be set it isn't used by the demonstration, but is still preserved for use with custom structures.

While testing I discovered that although CorelDRAW lets you enter quotes into the palette editor, it silently discarded any swatches with quotes in their name.

Reading the colour values

Although I don't go to crazy lengths, string parsing is typically quite expensive in terms of memory and/or processing power and so I often try and optimise this as I go.

In this case, while I could do string.Split, I'd rather avoid the array allocation so I'm manually looking for the number sequences. As we already have the end of the swatch name from the previous section, we can walk the rest of the string, discarding the white-space, finding the longest sequence of digits and then parsing out the result.

private byte NextNumber(string line, ref int start)
  int length;
  int valueLength;
  int maxLength;
  byte result;

  // skip any leading spaces
  while (char.IsWhiteSpace(line[start]))

  length = line.Length;
  maxLength = Math.Min(3, length - start);
  valueLength = 0;

  for (int i = 0; i < maxLength; i++)
    if (char.IsDigit(line[start + i]))

  result = byte.Parse(line.Substring(start, valueLength));

  start += valueLength;

  return result;

I start by iterating the string from an given start position and keep moving forward as long as the current character is a space.

Once I've ran out of spaces, I check how much of the string is left to determine the maximum number of characters to read - normally I want to read 3 as this is the maximum number of digits which can be present, however if it is at the end of the line and the final result isn't a 3 digit number there won't be enough data to read.

With the maximum number of digits established, I then check through these characters - if I find a digit, I increment a counter telling me how long the final number is. Anything else and I break out.

With both the start of the numeric field and its length I can then use byte.Parse on the substring.

It would be interesting to know if I could use the new Span<T> functionality in .NET Core to skip any string processing at all. An experiment for a future day!

The final action I perform is to record the new start position, so the next call to the method will continue from this point.

With this method in place, I can quite easily read out the four colour components without doing any array allocation.

int numberPosition;
byte cyan;
byte magenta;
byte yellow;
byte black;

numberPosition = lastQuote + 1;
cyan = this.NextNumber(line, ref numberPosition);
magenta = this.NextNumber(line, ref numberPosition);
yellow = this.NextNumber(line, ref numberPosition);
black = this.NextNumber(line, ref numberPosition);

Does it really matter?

Reading palettes is hardly going to be a performance critical operation, so maybe you don't need to write any exotic code. If you don't care about the allocations, you could write the value extraction a lot simpler by using string.Split. Note that even here, I still want to avoid "silent" allocations and use a prepared array with the split characters.

private readonly char[] _whitespace = { ' ' };

string[] parts;

parts = line.Substring(lastQuote + 1).Split(_whitespace, StringSplitOptions.RemoveEmptyEntries);
cyan = byte.Parse(parts[0]);
magenta = byte.Parse(parts[1]);
yellow = byte.Parse(parts[2]);
black = byte.Parse(parts[3]);

Personally, I prefer the former approach however I have to admit I didn't profile either approach as the article is overdue.

Converting CMYK to RGB

In absolutely no co-incidence at all, my previous article described how to convert between CMYK and RGB so I don't need to have a digression in this article.

private Color ConvertCmykToRgb(int c, int m, int y, int k)
  int r;
  int g;
  int b;
  float multiplier;

  multiplier = 1 - k / 100F;

  r = Convert.ToInt32(255 * (1 - c / 100F) * multiplier);
  g = Convert.ToInt32(255 * (1 - m / 100F) * multiplier);
  b = Convert.ToInt32(255 * (1 - y / 100F) * multiplier);

  return Color.FromArgb(r, g, b);

With this helper in place and regardless of which method you used to get the individual C, M, Y and K components, we can now fully read CorelDRAW 3.0 text based palettes into .NET.

_palette.Add(this.ConvertCmykToRgb(cyan, magenta, yellow, black));

Sample application

A familiar looking application demonstrating how to read CorelDRAW! 3.0 palettes
A familiar looking application demonstrating how to read CorelDRAW! 3.0 palettes

As usual, an example project demonstrating how to read CorelDRAW .pal files is available from the link below.

Update History

  • 2018-07-21 - First published
  • 2020-11-22 - Updated formatting

Like what you're reading? Perhaps you like to buy us a coffee?

Donate via Buy Me a Coffee

Donate via PayPal