Package net.sourceforge.jiu.color.quantization

Source Code of net.sourceforge.jiu.color.quantization.OctreeColorQuantizer

/*
* OctreeColorQuantizer
*
* Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007 Marco Schmidt.
* All rights reserved.
*/

package net.sourceforge.jiu.color.quantization;

import net.sourceforge.jiu.color.quantization.RGBQuantizer;
import net.sourceforge.jiu.data.MemoryPaletted8Image;
import net.sourceforge.jiu.data.Palette;
import net.sourceforge.jiu.data.Paletted8Image;
import net.sourceforge.jiu.data.PixelImage;
import net.sourceforge.jiu.data.RGB24Image;
import net.sourceforge.jiu.data.RGBIndex;
import net.sourceforge.jiu.ops.ImageToImageOperation;
import net.sourceforge.jiu.ops.MissingParameterException;
import net.sourceforge.jiu.ops.WrongParameterException;
import net.sourceforge.jiu.util.Sort;

/**
* Performs the octree color quantization algorithm for a given RGB truecolor image.
* The quality is usually somewhat inferior to the results of {@link MedianCutQuantizer}.
* Note that you can improve the quality by applying a dithering algorithm.
* See {@link net.sourceforge.jiu.color.dithering.ErrorDiffusionDithering}.
*
* <h3>Usage example</h3>
* This reduces some RGB24Image image to a 16 color paletted image:
* <pre>
* MemoryRGB24Image image = ...; // initialize
* OctreeColorQuantizer ocq = new OctreeColorQuantizer();
* ocq.setInputImage(image);
* ocq.setPaletteSize(16);
* ocq.process();
* PixelImage quantizedImage = ocq.getOutputImage();
* </pre>
*
* <h3>Credits</h3>
*
* @author Marco Schmidt
* @since 0.6.0
*/
public class OctreeColorQuantizer extends ImageToImageOperation implements RGBIndex, RGBQuantizer
{
  /**
   * The default number of colors in the palette.
   * Will be used when no other value is specified via {@link #setPaletteSize}.
   */
  public static final int DEFAULT_PALETTE_SIZE = 256;

  private int paletteSize = DEFAULT_PALETTE_SIZE;
  private OctreeNode root;
  private Palette palette;
  private int[] redValues;
  private int[] greenValues;
  private int[] blueValues;

  /**
   * If node is a leaf node, this method assigns palette index values
   * and determines the representative color, otherwise it simply
   * recursively calls itself for all child nodes.
   * The updated index value is returned.
   * It is increased whenever a leaf is assigned that index value.
   *
   * @param node the node of the octree that will itself (and its children) be processed
   * @param index the current index in the palette index assignment procedure
   * @return updated index value; may have been increased while node or its child(ren) -
   *  were assigned index values
   */
  private int assignPaletteIndexValues(OctreeNode node, int index)
  {
    if (node == null)
    {
      return index;
    }
    if (node.isLeaf())
    {
      node.setPaletteIndex(index);
      node.determineRepresentativeColor();
      return index + 1;
    }
    else
    {
      OctreeNode[] children = node.getChildren();
      if (children != null)
      {
        for (int i = 0; i < 8; i++)
        {
          if (children[i] != null)
          {
            index = assignPaletteIndexValues(children[i], index);
          }
        }
      }
      return index;
    }
  }

  public Palette createPalette()
  {
    if (palette == null)
    {
      int numValues = assignPaletteIndexValues(root, 0);
      palette = new Palette(numValues);
      initPalette(root, palette);
      redValues = new int[numValues];
      greenValues = new int[numValues];
      blueValues = new int[numValues];
      for (int i = 0; i < numValues; i++)
      {
        redValues[i] = palette.getSample(INDEX_RED, i);
        greenValues[i] = palette.getSample(INDEX_GREEN, i);
        blueValues[i] = palette.getSample(INDEX_BLUE, i);
      }
      return palette;
    }
    else
    {
      return (Palette)palette.clone();
    }
  }

  /**
   * Creates an octree and prepares this quantizer so that colors can be mapped to
   * palette index values.
   * If you use {@link #process()} you must not call this method.
   * On the other hand, if you want to do the mapping yourself - maybe if you
   * want to do mapping and dithering interchangeably - call this method first,
   * then do the mapping yourself.
   * @throws MissingParameterException if parameters like the input image are missing
   * @throws WrongParameterException if parameters exist but are of the wrong type
   */
  public void init() throws
    MissingParameterException,
    WrongParameterException
  {
    PixelImage pi = getInputImage();
    if (pi == null)
    {
      throw new MissingParameterException("Input image needed.");
    }
    if (!(pi instanceof RGB24Image))
    {
      throw new WrongParameterException("Input image must be of type RGB24Image.");
    }
    initOctree();
    pruneOctree();
  }

  private int initOctree()
  {
    int numUniqueColors = 0;
    root = new OctreeNode();
    RGB24Image in = (RGB24Image)getInputImage();
    for (int y = 0; y < in.getHeight(); y++)
    {
      for (int x = 0; x < in.getWidth(); x++)
      {
        int red = in.getSample(INDEX_RED, x, y);
        int green = in.getSample(INDEX_GREEN, x, y);
        int blue = in.getSample(INDEX_BLUE, x, y);
        if (OctreeNode.add(root, red, green, blue, 8))
        {
          numUniqueColors++;
        }
      }
    }
    root.copyChildSums();
    return numUniqueColors;
  }

  private void initPalette(OctreeNode node, Palette palette)
  {
    if (node == null)
    {
      return;
    }
    if (node.isLeaf())
    {
      int index = node.getPaletteIndex();
      palette.put(index, node.getRed(), node.getGreen(), node.getBlue());
      return;
    }
    OctreeNode[] children = node.getChildren();
    if (children == null)
    {
      return;
    }
    for (int i = 0; i < children.length; i++)
    {
      OctreeNode child = children[i];
      if (child != null)
      {
        initPalette(child, palette);
      }
    }
  }

  /**
   * Maps an RGB color <code>origRgb</code> to one of the colors in the
   * color map; that color will be written to <code>quantizedRgb</code>
   * and its palette index will be returned.
   * @param origRgb the color to be mapped to the best-possible counterpart in the
   *  palette; the array is indexed by the constants from {@link RGBIndex}
   * @param quantizedRgb the resulting color from the palette will be written
   *  to this array; it is also indexed by the constants from {@link RGBIndex}
   * @return index of the found color in the palette
   */
  public int map(int[] origRgb, int[] quantizedRgb)
  {
    int result = root.map(origRgb, quantizedRgb);
    if (result == -1)
    {
      int minIndex = 0;
      int minDistance = Integer.MAX_VALUE;
      int i = 0;
      int red = origRgb[INDEX_RED];
      int green = origRgb[INDEX_GREEN];
      int blue = origRgb[INDEX_BLUE];
      while (i < redValues.length)
      {
        int v = (redValues[i] - red);
        int sum = v * v;
        v = (greenValues[i] - green);
        sum += v * v;
        v = (blueValues[i] - blue);
        sum += v * v;
        if (sum < minDistance)
        {
          minIndex = i;
          minDistance = sum;
        }
        i++;
      }
      quantizedRgb[INDEX_RED] = redValues[minIndex];
      quantizedRgb[INDEX_GREEN] = greenValues[minIndex];
      quantizedRgb[INDEX_BLUE] = blueValues[minIndex];
      return minIndex;
    }
    else
    {
      // quantizedRgb was filled with values in root.map
      return result;
    }
  }

  private void mapImage()
  {
    RGB24Image in = (RGB24Image)getInputImage();
    Palette palette = createPalette();
    Paletted8Image out = new MemoryPaletted8Image(in.getWidth(), in.getHeight(), palette);
    int[] origRgb = new int[3];
    int[] quantizedRgb = new int[3];
    for (int y = 0; y < in.getHeight(); y++)
    {
      for (int x = 0; x < in.getWidth(); x++)
      {
        origRgb[INDEX_RED] = in.getSample(INDEX_RED, x, y);
        origRgb[INDEX_GREEN] = in.getSample(INDEX_GREEN, x, y);
        origRgb[INDEX_BLUE] = in.getSample(INDEX_BLUE, x, y);
        int index = map(origRgb, quantizedRgb);
        out.putSample(0, x, y, index);
      }
    }
    setOutputImage(out);
  }

  /**
   * Initializes an octree, reduces it have as many leaves (or less) as
   * the desired palette size and maps the original image to the newly-created
   * palette.
   */
  public void process() throws
    MissingParameterException,
    WrongParameterException
  {
    init();
    mapImage();
  }

  /**
   * Reduces the octree until it has as many leaves (or less) than specified
   * by the <code>paletteSize</code> argument in the constructor
   * {@link #OctreeColorQuantizer(int)}.
   */
  private void pruneOctree()
  {
    // create an array for as many palette entries as specified by paletteSize
    OctreeNode[] a = new OctreeNode[paletteSize];
    // initial length is 1, the only element is the root
    a[0] = root;
    int length = 1;
    // create a comparator that will be used to sort the nodes by their pixel count,
    // in ascending order
    OctreeNode comparator = new OctreeNode();
    // loop and split leaves as long as the number of nodes (length) is smaller
    // than the desired palette size
    while (length < paletteSize)
    {
      Sort.sort(a, 0, length - 1, comparator);
      int index = length - 1;
      while (index >= 0)
      {
        OctreeNode node = a[index];
        int numChildren = node.getNumChildren();
        // check if current length minus the node which may be split
        // plus the number of its children does not exceed the desired palette size
        if (numChildren > 0 && length - 1 + numChildren < paletteSize)
        {
          // child nodes fit in here
          if (index < length - 1)
          {
            System.arraycopy(a, index + 1, a, index, length - index - 1);
          }
          length--;
          OctreeNode[] children = node.getChildren();
          for (int i = 0; i < children.length; i++)
          {
            if (children[i] != null)
            {
              a[length++] = children[i];
            }
          }
          break;
        }
        else
        {
          index--;
        }
      }
      if (index == -1)
      {
        // we could not find a node to be split
        break;
      }
    }
    // in some cases it is not possible to get exactly paletteSize leaves;
    // adjust paletteSize to be equal to the number of leaves
    // note that length will never be larger than paletteSize, only smaller
    // in some cases
    paletteSize = length;
    // make all found nodes leaves by setting their child nodes to null
    for (int i = 0; i < length; i++)
    {
      a[i].setChildren(null);
    }
  }

  public void setPaletteSize(int newPaletteSize)
  {
    if (newPaletteSize < 1)
    {
      throw new IllegalArgumentException("Palette size must be 1 or larger.");
    }
    if (newPaletteSize > 256)
    {
      throw new IllegalArgumentException("Palette size must be 256 or smaller.");
    }
    paletteSize = newPaletteSize;
  }
}
TOP

Related Classes of net.sourceforge.jiu.color.quantization.OctreeColorQuantizer

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.