Simple algorithms for adjusting image temperature and tint

I’ve already talked at length about converting a temperature (in Kelvin) to an RGB triplet. But what if you simply want to adjust an image’s temperature, without caring about the specifics of it?

Here’s how:

Given a temperature adjustment on the range -100 to 100,
 apply the following adjustment to each pixel in the image:

r = r + adjustmentValue
g = g
b = b - adjustmentValue

As with any additive RGB adjustment, you’ll need to manually clip the output values to the [0, 255] range.

Here’s a sample of the output, as implemented in the latest development build of my open-source photo editor. Note the temperature slider at the bottom of the screen. Because I find the -100 to 100 range to be a bit too strong, I actually divide the adjustment value by 5, thus limiting the actual adjustment value from -20 to 20:

temp_tint_base_image
Base image, courtesy of http://en.wikipedia.org/wiki/Great_wall
Temperature at max value (+20)
Temperature at max value (+20). Note the warmer tones.
Temperature at min value (-20)
Temperature at min value (-20). Note the cooler tones.

Pretty simple!

Because temperature and tint adjustments are usually provided together, here’s the code for basic tint adjustments. It’s even simper than temperature:

Given a tint adjustment on the range -100 to 100, 
 apply the following adjustment to each pixel in the image:

r = r 
g = g + adjustmentValue
b = b 

Sample output, using the same sample image from above (note the tint slider at the bottom):

Tint at max value (+20).  Hard to tell as the image is already very green, but the green has actually been ramped up further.
Tint at max value (+20). Hard to tell as the image is already very green, but the green has actually been ramped up further.
Tint at minimum value (-20).  Note the magenta color cast.
Tint at minimum value (-20). Note the magenta color cast.

This might be the simplest image processing algorithm I’ve ever posted… :)

Announcing PhotoDemon 5.0 – Everything is Faster, Everything is Better

Summary

PhotoDemon v5.0 is now available. It’s the biggest update PhotoDemon has seen in years, and it’s awesome. Download it here.

PhotoDemon 5.0 boasts a ton of improvements – both on the surface and under the hood.

New Feature: All-New Image Subsystem

In version 5.0, the way PhotoDemon stores and processes image data has been rewritten from scratch. What does this mean for you?

  • Filters, effects, and all tools are faster than version 4.4.
  • The software uses roughly half the RAM of previous versions.
  • No more upper limit on image sizes. Huge photos (30+ megapixel) should work just fine on any modern PC. The only limiting factor is the amount of RAM (actual and virtual) available on your system.
  • Much faster batch conversions. As an example of how much better version 5.0 is: I ran two identical batch conversions of 138 wedding photos (10 megapixels each, 3872×2592 pixels). The batch conversion was simple – load each image, then save it in another folder at a different JPEG quality. PhotoDemon 4.4 performed the conversion in 2 minutes 21 seconds. PhotoDemon 5.0 does it in 1 minute 11 seconds.
  • Much better OSX and Linux compatibility via Wine. (Wine v1.4 or later is required.)

This sole feature was the largest update PhotoDemon has seen in the past five years. As a teaser, the new subsystem is also compatible with selections and layers, which may make an appearance in a future update…

New Feature: Alpha-Channel (Transparency) Support

For the first time in the history of the program, PhotoDemon now provides proper transparency support. When images with an alpha-channel are loaded, PhotoDemon will automatically maintain the transparency data for the life of the image. When the image is saved to file, the alpha-channel is added back in, allowing you to do any amount of edits to images without harming the underlying alpha data.

Transformations like resizing and rotating also preserve the alpha channel. (Again, this was a prerequisite to features like layers… see a pattern here?)

New Feature: Redesigned Interface

Every menu item in PhotoDemon now has a descriptive icon, and menus have been reorganized according to improved design rules. No menu is more than two layers deep, and new accelerators (hotkeys) have been added to popular features.

The redesigned Color menu

The left-hand bar has been updated once again. Per feedback from users, a dedicated Close and Save As button has been added, along with descriptive text for each button. Tool-tips have also been added to each button. (Thanks to Robert Rayment for the suggestion!) Finally, the zoom box has been rebuilt with a new, more useful set of zoom values.

New left-hand bar in 5.0, including descriptive tool-tips.

All preview boxes have been enlarged on tool, filter, and effect windows. Text has also been enlarged to improve readability. PhotoDemon was originally designed to run on 800×600 resolutions (that was a concern in 2001!) but there’s no need for it to remain so compact in 2012.

The old and new edge detection tools
The old and new Custom Filter tools

Finally, a new View menu has been added to provide compatibility with other popular photo editors. The new menu is a great place to discover all the useful hotkeys (also called “accelerators”) for popular zoom functions. The key listed on the right-hand side of a menu item can be used as a shortcut to that menu – so pressing the “+” key will zoom in, the “-” key will zoom out, and the “0” key will instantly fit the entire image on the screen.

The new View menu

New Feature: All-New Image Load/Save Engine

PhotoDemon 5.0 uses a completely new system for getting images into – and out of – the program. As you may know, the program relies on an outside library called FreeImage for supporting non-standard image formats like Photoshop files (PSD), Macintosh PICT files (PICT), DirectDraw surfaces (DDS), and more.

FreeImage is an excellent tool, but its implementation in past versions of PhotoDemon was very rudimentary. PhotoDemon relied on FreeImage to do its own image file type detection, configure each image type properly, and prepare it for use within the program. While it was pretty good at guessing these parameters, it was not foolproof, and odd color-depths, transparencies, and mismatched file extensions could result in failed image loads or even program crashes.

So for version 5.0, the FreeImage interface was rewritten from the ground up. When images are loaded, a fallback system is used to identify the file format – first the file header is compared against a database of known filetypes. That works for 95+% of files. If for some reason a header cannot be found (which is the case with some formats, including outliers like CUT, MNG, PCD, TGA and WBMP), the image’s file extension is then analyzed. If that fails, PhotoDemon will attempt to blindly load bitmap data and hope for the best. And, if even that fails, PhotoDemon will give the image one final try by passing control off to the Windows’ GDI+ system and seeing if it can decipher the file.

This should make PhotoDemon as robust as possible when loading images. (Thanks to Herman Liu for much testing and help with the new image import implementation!) The full list of file formats supported by PhotoDemon now includes:

Importing:

  • BMP – Windows Bitmap
  • DDS – DirectDraw Surface
  • GIF – Compuserve
  • ICO – Windows Icon
  • IFF – Amiga Interchange Format
  • JNG – JPEG Network Graphics
  • JPG/JPEG – Joint Photographic Experts Group
  • KOA/KOALA – Commodore 64
  • LBM – Deluxe Paint
  • MNG – Multiple Network Graphics
  • PBM – Portable Bitmap
  • PCD – Kodak PhotoCD
  • PCX – Zsoft Paintbrush (uncompressed only)
  • PDI – PhotoDemon Image (the program’s native format)
  • PGM – Portable Greymap
  • PIC/PICT – Macintosh Picture
  • PNG – Portable Network Graphic
  • PPM – Portable Pixmap
  • PSD – Adobe Photoshop
  • RAS – Sun Raster File
  • SGI/RGB/BW – Silicon Graphics Image
  • TGA – Truevision Targa
  • TIF/TIFF – Tagged Image File Format
  • WBMP – Wireless Bitmap

Exporting:

  • BMP – Windows Bitmap
  • GIF – Graphics Interchange Format
  • JPG – Joint Photographic Experts Group
  • PDI – PhotoDemon Image (the program’s native format)
  • PNG – Portable Network Graphic
  • PPM – Portable Pixel Map
  • TGA – Truevision Targa
  • TIFF – Tagged Image File Format

New Feature: Color Temperature Tool

A full discussion of color temperature and how it works is available at this Wikipedia article, but a simple description is: color temperature allows you to retroactively adjust the lighting of a photograph. It’s a powerful way to change the mood of a photo, or to adjust lighting to reflect how you remember a scene – versus what the camera actually caught.

The all-new Color Temperature tool. To my knowledge, no other free photo editor provides a tool like this.

I’m quite proud of this tool, in part because it took a ridiculous amount of work to build. Other free photo editors like GIMP and Paint.NET lack anything like this, so short of Photoshop, PhotoDemon is one of the only software programs to provide such a feature.

The image below – a promotional poster for the HBO series True Blood – nicely demonstrates the potential of color temperature adjustments. On the left is the original shot; on the right, a color temperature adjustment using PhotoDemon. In one click, a nighttime scene can been recast in daylight.

Color temperature adjustment in action.

New Feature: Black and White (1-bit) Conversion

PhotoDemon already possesses a powerful grayscale engine, with more conversion options than any other tool on the market. But what if you want to literally convert an image to black and white – as in just black and just white?

Now you can, thanks to a revamped black-and-white tool.

The new black-and-white tool, rewritten from scratch for 5.0.

The new tool operates hand-in-hand with a flexible, powerful dithering engine. The new engine design allows for any combination of dithering and threshold, and if you’d like, you can also have PhotoDemon estimate an ideal threshold value for a given image. (An ideal threshold is one that leads to an image that’s roughly 50% black and 50% white.)

A comprehensive assortment of dithering algorithms is provided, including: Bayer 4×4 and 8×8, False (fast) Floyd-Steinberg, Genuine Floyd-Steinberg, Jarvis/Judice/Ninke, Stucki, Burkes, Sierra-3, Two-Row Sierra, Sierra Lite, and my personal favorite – Bill Atkinson’s classic Macintosh algorithm, which featured prominently in the original Apple Macintosh. Images treated with this algorithm evoke a certain nostalgia for anyone old enough to remember that era of computing.

Atkinson dithering, as applied to a screen capture from a Warehouse 13 episode.

New Feature: Tile Tool

Have you ever needed to tile an image? There are a lot of ways to do it. Most involve copying-and-pasting an image over and over again, then manually arranging those copies into a grid.

I hate tedious tasks like that. So PhotoDemon has a new tool that makes tiling a trivial operation.

The new Tile tool.

You can tile according to three rules: the current screen size (automatically detected), a set size in pixels, or a set number of tiles. The tool will automatically convert between each system for you, and it will let you know the size of the final image in both tiles and pixels.

Other new features and updates in version 5.0

Other updates in v5.0 include:

  • New “Duplicate Image” tool. Perfect for making a working copy of an image without fear of overwriting the original. (Thanks to Achmad Junus for the suggestion!)
  • Drag-and-Drop compatibility. Drag images from your desktop or file manager onto PhotoDemon, and it will open them all automatically. (Thanks to Kroc of camendesign.com for the suggestion!)
  • Auto-Enhance overhaul. All four auto-enhance tools (contrast, highlights, midtones, shadows) have been rewritten from scratch using completely new algorithms. I think you’ll find them way more useful than the old tools.
  • Improved mosaic tool. Faster, higher quality, and mosaics can now be as large as the image or as tiny as one pixel in either dimension.
  • Improved handling of edge pixels for all convolution filters (blur, soften, sharpen, etc)
  • Improved manual color reduction algorithms (faster and higher quality)
  • New histogram equalization form. Equalize any combination of color channels (red, green, blue) and luminance with real-time previews.
  • DPI-aware images mean no more distortion at 120dpi – a big improvement for people using “large font” settings.
  • Fixes for users of the “Classic Theme” in modern versions of Windows. Your menus should look much better in this release.
  • Improved bug reporting system and online form to match.
  • Tons of miscellaneous bug fixes, tweaks, and optimizations. For a full list of changes, visit https://github.com/tannerhelland/PhotoDemon/commits/master

In Conclusion…

I hope you enjoy the many improvements in version 5.0. As always, feel free to contact me with any feedback you might have.

How to Convert Temperature (K) to RGB: Algorithm and Sample Code

Converting temperature (Kelvin) to RGB: an overview

If you don’t know what “color temperature” is, start here.

While working on a “Color Temperature” tool for PhotoDemon, I spent an evening trying to track down a simple, straightforward algorithm for converting between temperature (in Kelvin) and RGB values. This seemed like an easy algorithm to find, since many photo editors provide tools for correcting an image’s color temperature in post-production, and every modern camera – including smartphones – provides a way to adjust white balance based on the lighting conditions of a shot.

Example of a camera white balance screen. Image courtesy of http://digitalcamerareviews2011online.blogspot.com

Little did I know, but it’s pretty much impossible to find a reliable temperature to RGB conversion formula. Granted, there are some algorithms out there, but most work by converting temperature to the XYZ color space, to which you could add your own RGB transformation after the fact. Such algorithms seem to be based off AR Robertson’s method, one implementation of which is here, while another is here.

Unfortunately, that approach isn’t really a mathematical formula – it’s just glorified look-up table interpolation. That might be a reasonable solution under certain circumstances, but when you factor in the additional XYZ -> RGB transformation required, it’s just too slow and overwrought for simple real-time color temperature adjustment.

So I wrote my own algorithm, and it works pretty damn well. Here’s how I did it.

Caveats for using this algorithm

Caveat 1: my algorithm provides a high-quality approximation, but it’s not accurate enough for serious scientific use. It’s designed primarily for photo manipulation – so don’t try and use it for astronomy or medical imaging.

Caveat 2: due to its relative simplicity, this algorithm is fast enough to work in real-time on reasonably sized images (I tested it on 12 megapixel shots), but for best results you should apply mathematical optimizations specific to your programming language. I’m presenting it here without math optimizations so as to not over-complicate it.

Caveat 3: this algorithm is only designed to be used between 1000 K and 40000 K, which is a nice spectrum for photography. (Actually, it’s way larger than most photographic situations will ever call for.) While it will work for temperatures outside these ranges, the estimation quality will decline.

Special thanks to Mitchell Charity

First off, I owe a big debt of gratitude to the source data I used to generate these algorithms – Mitchell Charity’s raw blackbody datafile at http://www.vendian.org/mncharity/dir3/blackbody/UnstableURLs/bbr_color.html. Charity provides two datasets, and my algorithm uses the CIE 1964 10-degree color matching function. A discussion of the CIE 1931 2-degree CMF with Judd Vos corrections versus the 1964 10-degree set is way beyond the scope of this article, but you can start here for a more comprehensive analysis if you’re so inclined.

The Algorithm: sample output

Here’s the output of the algorithm from 1000 K to 40000 K:

Output of my algorithm from 1000 K to 40000 K. The white point occurs at 6500-6600 K, which is perfect for photo manipulation purposes on a modern LCD monitor.

Here’s a more detailed shot of the algorithm in the interesting photographic range, which is 1500 K to 15000 K:

Same algorithm, but from 1500 K to 15000 K

As you can see, banding is minimal – which is a big improvement over the aforementioned look-up table methods. The algorithm also does a great job of preserving the slightly yellow cast leading up to the white point, which is important for imitating daylight in post-production photo manipulation.

How I arrived at this algorithm

My first step in reverse-engineering a reliable formula was to plot Charity’s original blackbody values. You can download my whole worksheet here in LibreOffice / OpenOffice .ods format (430kb).

Here’s how the data looks when plotted:

Mitchell Charity’s original Temperature (K) to RGB (sRGB) data, plotted in LibreOffice Calc. Again, these are based off the CIE 1964 10-degree CMFs. The white point, as desired, occurs between 6500 K and 6600 K (the peak on the left-hand side of the chart). (Source: http://www.vendian.org/mncharity/dir3/blackbody/UnstableURLs/bbr_color.html)

From this, it’s easy to note that there are a few floors and ceilings that make our algorithm easier. Specifically:

  • Red values below 6600 K are always 255
  • Blue values below 2000 K are always 0
  • Blue values above 6500 K are always 255

It’s also important to note that for purposes of fitting a curve to the data, green is best treated as two separate curves – one for temperatures below 6600 K, and a separate one for temperatures above that point.

From here, I separated the data (without the “always 0” and “always 255” segments) into individual color components. In a perfect world, a curve could then be fitted to each set of points, but unfortunately it wasn’t that simple. Because there’s a large disparity between the X and Y values in the plot – the x-values are all over 1000, and they are plotted in 100 point segments, while the y values all fall between 255 and 0 – it was necessary to transpose the x data in order to get a better fit. For optimization purposes, I stuck to first dividing the x value (the temperature) by 100 across each color, followed by an additional subtraction if it led to a significantly better fit. Here are the resultant charts for each curve, along with the best-fit curve and corresponding R-squared value:

Apologies for the horrifically poor font kerning and hinting in those charts. I love LibreOffice for many things, but its inability to do font aliasing on charts is downright shameful. I also don’t like having to extract charts from screenshots because they don’t have an export option, but that’s a rant best saved for some other day.

As you can see, the curves all fit reasonably well, with R-square values above .987. I could have spent more time really tweaking the curves, but for purposes of photo manipulation these are plenty close enough. No layperson is going to be able to tell that the curves don’t exactly fit raw idealized blackbody observations, right?

The algorithm

Using that data, here’s the algorithm, in all its glory.

First, pseudocode:



    Start with a temperature, in Kelvin, somewhere between 1000 and 40000.  (Other values may work,
     but I can't make any promises about the quality of the algorithm's estimates above 40000 K.)
    Note also that the temperature and color variables need to be declared as floating-point.

    Set Temperature = Temperature \ 100
    
    Calculate Red:

    If Temperature <= 66 Then
        Red = 255
    Else
        Red = Temperature - 60
        Red = 329.698727446 * (Red ^ -0.1332047592)
        If Red < 0 Then Red = 0
        If Red > 255 Then Red = 255
    End If
    
    Calculate Green:

    If Temperature <= 66 Then
        Green = Temperature
        Green = 99.4708025861 * Ln(Green) - 161.1195681661
        If Green < 0 Then Green = 0
        If Green > 255 Then Green = 255
    Else
        Green = Temperature - 60
        Green = 288.1221695283 * (Green ^ -0.0755148492)
        If Green < 0 Then Green = 0
        If Green > 255 Then Green = 255
    End If
    
    Calculate Blue:

    If Temperature >= 66 Then
        Blue = 255
    Else

        If Temperature <= 19 Then
            Blue = 0
        Else
            Blue = Temperature - 10
            Blue = 138.5177312231 * Ln(Blue) - 305.0447927307
            If Blue < 0 Then Blue = 0
            If Blue > 255 Then Blue = 255
        End If

    End If

In the pseudocode above, note that Ln() means natural logarithm. Note also that you can omit the “If color < 0” checks if you will only ever supply temperatures in the recommended range. (You still need to leave the “If color > 255” checks, though.)

As for actual code, here’s the exact Visual Basic function I’m using in PhotoDemon. It’s not yet optimized (for example, the logarithms would be much faster via look-up table) but at least the code is short and readable:


'Given a temperature (in Kelvin), estimate an RGB equivalent
Private Sub getRGBfromTemperature(ByRef r As Long, ByRef g As Long, ByRef b As Long, ByVal tmpKelvin As Long)

    Static tmpCalc As Double

    'Temperature must fall between 1000 and 40000 degrees
    If tmpKelvin < 1000 Then tmpKelvin = 1000
    If tmpKelvin > 40000 Then tmpKelvin = 40000
    
    'All calculations require tmpKelvin \ 100, so only do the conversion once
    tmpKelvin = tmpKelvin \ 100
    
    'Calculate each color in turn
    
    'First: red
    If tmpKelvin <= 66 Then
        r = 255
    Else
        'Note: the R-squared value for this approximation is .988
        tmpCalc = tmpKelvin - 60
        tmpCalc = 329.698727446 * (tmpCalc ^ -0.1332047592)
        r = tmpCalc
        If r < 0 Then r = 0
        If r > 255 Then r = 255
    End If
    
    'Second: green
    If tmpKelvin <= 66 Then
        'Note: the R-squared value for this approximation is .996
        tmpCalc = tmpKelvin
        tmpCalc = 99.4708025861 * Log(tmpCalc) - 161.1195681661
        g = tmpCalc
        If g < 0 Then g = 0
        If g > 255 Then g = 255
    Else
        'Note: the R-squared value for this approximation is .987
        tmpCalc = tmpKelvin - 60
        tmpCalc = 288.1221695283 * (tmpCalc ^ -0.0755148492)
        g = tmpCalc
        If g < 0 Then g = 0
        If g > 255 Then g = 255
    End If
    
    'Third: blue
    If tmpKelvin >= 66 Then
        b = 255
    ElseIf tmpKelvin <= 19 Then
        b = 0
    Else
        'Note: the R-squared value for this approximation is .998
        tmpCalc = tmpKelvin - 10
        tmpCalc = 138.5177312231 * Log(tmpCalc) - 305.0447927307
        
        b = tmpCalc
        If b < 0 Then b = 0
        If b > 255 Then b = 255
    End If
    
End Sub

This function was used to generate the sample output near the start of this article, so I can guarantee that it works.

Sample images

Here’s a great example of what color temperature adjustments can do. The image below – a promotional poster for the HBO series True Blood – nicely demonstrates the potential of color temperature adjustments. On the left is the original shot; on the right, a color temperature adjustment using the code above. In one click, a nighttime scene can been recast in daylight.

Color temperature adjustments in action. (Click for full size)

The actual color temperature tool in my PhotoDemon project looks like this:

PhotoDemon’s Color Temperature tool.

Download it here to see it in action.

addendum October 2014

Renaud Bédard has put together a great online demonstration of this algorithm. Check it out here, and thanks to Renaud for sharing!

addendum April 2015

Thank you to everyone who has suggested improvements to the original algorithm. I know there are a lot of comments on this article, but they’re worth reading if you’re planning on implementing your own version.

I’d like to call out two specific improvements. First, Neil B has helpfully provided a better version of the original curve-fitting functions, which results in slightly modified temperature coefficients. His excellent article describes the changes in detail.

Next, Francis Loch has added some comments and sample images below, which are very helpful if you want to apply these corrections to a photograph. His modifications produce a much more detailed image, as his sample images demonstrate.