Package net.sourceforge.jiu.color.dithering

Source Code of net.sourceforge.jiu.color.dithering.OrderedDither

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

package net.sourceforge.jiu.color.dithering;

import net.sourceforge.jiu.color.quantization.UniformPaletteQuantizer;
import net.sourceforge.jiu.data.BilevelImage;
import net.sourceforge.jiu.data.Gray8Image;
import net.sourceforge.jiu.data.MemoryBilevelImage;
import net.sourceforge.jiu.data.MemoryGray8Image;
import net.sourceforge.jiu.data.MemoryPaletted8Image;
import net.sourceforge.jiu.data.MemoryRGB24Image;
import net.sourceforge.jiu.data.PixelImage;
import net.sourceforge.jiu.data.Paletted8Image;
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;

/**
* This operation reduces the color depth of RGB truecolor images and grayscale images
* by applying ordered dithering.
* A relatively nice output image (for a human observer) will be created at the
* cost of introducing noise into the output image.
* The algorithm is relatively fast, but its quality is usually inferior to what
* can be reached by using {@link ErrorDiffusionDithering} or similar other methods.
* <h3>Supported conversions</h3>
* <ul>
* <li><strong>Grayscale to bilevel</strong> maps GrayIntegerImage objects
*  to BilevelImage objects.</li>
* <li><strong>Grayscale to grayscale</strong> maps GrayIntegerImage objects
*  to other GrayIntegerImage objects.
*  Right now, only Gray8Image objects are supported.
*  After reducing the number of bits, each sample is immediately scaled back
*  to 8 bits per pixel in order to fit into another Gray8Image object.</li>
* <li><strong>Truecolor to paletted</strong> maps RGBIntegerImage objects
*  to Paletted8Image objects; the sum of the output bits must not be larger
*  than 8</li>
* </ul>
* <h3>Usage example</h3>
* The following code snippet demonstrates how to create a paletted
* image with 256 colors, using three bits for red and green and two
* bits for blue, from an RGB truecolor image.
* <pre>
* OrderedDither od = new OrderedDither();
* od.setRgbBits(3, 3, 2);
* od.setInputImage(image);
* od.process();
* Paletted8Image ditheredImage = (Paletted8Image)od.getOutputImage();
* </pre>
* @author Marco Schmidt
*/
public class OrderedDither extends ImageToImageOperation implements RGBIndex
{
  private int[] values;
  private int valueWidth;
  private int valueHeight;
  private int grayBits = 3;
  private int redBits = 3;
  private int greenBits = 3;
  private int blueBits = 2;

  private void process(Gray8Image in, Gray8Image out)
  {
    if (out == null)
    {
      out = new MemoryGray8Image(in.getWidth(), in.getHeight());
      setOutputImage(out);
    }
    int D1 = 4;
    int D2 = 4;
    int D1D2 = D1 * D2;
    final int[] DITHER_MATRIX = {0, 8, 2, 10, 12, 4, 14, 6, 3, 11, 1, 9, 15, 7, 13, 5};
    final int SPACE = 255 / ((1 << grayBits) - 1);
    final int SHIFT = 8 - grayBits;
    final int NUM_VALUES = 1 << grayBits;
    final byte[] OUTPUT_SAMPLES = new byte[NUM_VALUES];
    for (int i = 0; i < OUTPUT_SAMPLES.length; i++)
    {
      OUTPUT_SAMPLES[i] = (byte)((i * 255) / NUM_VALUES);
    }
    final int[] DITHER_SIGNAL = new int[D1D2];
    for (int i = 0; i < D1D2; i++)
    {
      DITHER_SIGNAL[i] = (2 * DITHER_MATRIX[i] - D1D2 + 1) * SPACE / (2 * D1D2);
    }
    final int HEIGHT = in.getHeight();
    final int WIDTH = in.getWidth();
    int rowOffset = 0;
    for (int y = 0; y < HEIGHT; y++)
    {
      int offset = rowOffset;
      final int MAX_OFFSET = rowOffset + D1;
      for (int x = 0; x < WIDTH; x++)
      {
        int sample = in.getSample(0, x, y);
        sample = sample + DITHER_SIGNAL[ offset++ ];
        if (offset == MAX_OFFSET)
        {
          offset = rowOffset;
        }
        if (sample < 0)
        {
          sample = 0;
        }
        else
        if (sample > 255)
        {
          sample = 255;
        }
        out.putByteSample(0, x, y, OUTPUT_SAMPLES[sample >> SHIFT]);
      }
      rowOffset += D1;
      if (rowOffset >= DITHER_SIGNAL.length)
      {
        rowOffset = 0;
      }
      setProgress(y, HEIGHT);
    }
  }

  private void process(Gray8Image in, BilevelImage out)
  {
    if (out == null)
    {
      out = new MemoryBilevelImage(in.getWidth(), in.getHeight());
      setOutputImage(out);
    }
    if (values == null)
    {
      setStandardThresholdValues();
    }
    out.clear(BilevelImage.BLACK);
    int rowOffset = 0;
    final int HEIGHT = in.getHeight();
    for (int y = 0; y < HEIGHT; y++)
    {
      int offset = rowOffset;
      final int MAX_OFFSET = rowOffset + valueWidth;
      for (int x = 0; x < in.getWidth(); x++)
      {
        if (in.getSample(x, y) >= values[offset++])
        {
          out.putWhite(x, y);
        }
        if (offset == MAX_OFFSET)
        {
          offset = rowOffset;
        }
      }
      setProgress(y, HEIGHT);
      rowOffset += valueWidth;
      if (rowOffset >= values.length)
      {
        rowOffset = 0;
      }
    }
  }

  private void process(RGB24Image in, Paletted8Image out)
  {
    UniformPaletteQuantizer upq = new UniformPaletteQuantizer(redBits, greenBits, blueBits);
    if (out == null)
    {
      out = new MemoryPaletted8Image(in.getWidth(), in.getHeight(), upq.createPalette());
      setOutputImage(out);
    }
    int D1 = 4;
    int D2 = 4;
    int D1D2 = D1 * D2;
    final int[] DITHER_MATRIX = {0, 8, 2, 10, 12, 4, 14, 6, 3, 11, 1, 9, 15, 7, 13, 5};
    final int RED_SPACE = 255 / ((1 << redBits) - 1);
    final int GREEN_SPACE = 255 / ((1 << greenBits) - 1);
    final int BLUE_SPACE = 255 / ((1 << blueBits) - 1);
    /*final int RED_SHIFT = 8 - redBits;
    final int GREEN_SHIFT = 8 - redBits;
    final int BLUE_SHIFT = 8 - redBits;
    final int NUM_RED_VALUES = 1 << redBits;
    final int NUM_GREEN_VALUES = 1 << greenBits;
    final int NUM_BLUE_VALUES = 1 << blueBits;*/
    final int[] RED_DITHER_SIGNAL = new int[D1D2];
    final int[] GREEN_DITHER_SIGNAL = new int[D1D2];
    final int[] BLUE_DITHER_SIGNAL = new int[D1D2];
    for (int i = 0; i < D1D2; i++)
    {
      RED_DITHER_SIGNAL[i] = (2 * DITHER_MATRIX[i] - D1D2 + 1) * RED_SPACE / (2 * D1D2);
      GREEN_DITHER_SIGNAL[i] = (2 * DITHER_MATRIX[i] - D1D2 + 1) * GREEN_SPACE / (2 * D1D2);
      BLUE_DITHER_SIGNAL[i] = (2 * DITHER_MATRIX[i] - D1D2 + 1) * BLUE_SPACE / (2 * D1D2);
    }
    final int HEIGHT = in.getHeight();
    final int WIDTH = in.getWidth();
    int rowOffset = 0;
    for (int y = 0; y < HEIGHT; y++)
    {
      int offset = rowOffset;
      final int MAX_OFFSET = rowOffset + D1;
      for (int x = 0; x < WIDTH; x++)
      {
        int redSample = in.getSample(INDEX_RED, x, y);
        redSample = redSample + RED_DITHER_SIGNAL[ offset ];
        if (redSample < 0)
        {
          redSample = 0;
        }
        else
        if (redSample > 255)
        {
          redSample = 255;
        }
        int greenSample = in.getSample(INDEX_GREEN, x, y);
        greenSample = greenSample + GREEN_DITHER_SIGNAL[ offset ];
        if (greenSample < 0)
        {
          greenSample = 0;
        }
        else
        if (greenSample > 255)
        {
          greenSample = 255;
        }
        int blueSample = in.getSample(INDEX_BLUE, x, y);
        blueSample = blueSample + BLUE_DITHER_SIGNAL[ offset ];
        if (blueSample < 0)
        {
          blueSample = 0;
        }
        else
        if (blueSample > 255)
        {
          blueSample = 255;
        }
        out.putSample(0, x, y, upq.mapToIndex(redSample, greenSample, blueSample));
        offset++;
        if (offset == MAX_OFFSET)
        {
          offset = rowOffset;
        }
      }
      rowOffset += D1;
      if (rowOffset >= DITHER_MATRIX.length)
      {
        rowOffset = 0;
      }
      setProgress(y, HEIGHT);
    }
  }

  private void process(RGB24Image in, RGB24Image out)
  {
    //UniformPaletteQuantizer upq = new UniformPaletteQuantizer(redBits, greenBits, blueBits);
    //System.out.println("RGB=>RGB, r=" + redBits+  ", g=" + greenBits + ", b=" + blueBits);
    if (out == null)
    {
      out = new MemoryRGB24Image(in.getWidth(), in.getHeight());
      setOutputImage(out);
    }
    int D1 = 4;
    int D2 = 4;
    int D1D2 = D1 * D2;
    final int[] DITHER_MATRIX = {0, 8, 2, 10, 12, 4, 14, 6, 3, 11, 1, 9, 15, 7, 13, 5};
    final int RED_SPACE = 255 / ((1 << redBits) - 1);
    final int GREEN_SPACE = 255 / ((1 << greenBits) - 1);
    final int BLUE_SPACE = 255 / ((1 << blueBits) - 1);
    final int RED_SHIFT = 8 - redBits;
    final int GREEN_SHIFT = 8 - greenBits;
    final int BLUE_SHIFT = 8 - blueBits;
    final int MAX_RED = (1 << redBits) - 1;
    final int MAX_GREEN = (1 << greenBits) - 1;
    final int MAX_BLUE = (1 << blueBits) - 1;
    /*final int NUM_RED_VALUES = 1 << redBits;
    final int NUM_GREEN_VALUES = 1 << greenBits;
    final int NUM_BLUE_VALUES = 1 << blueBits;*/
    final int[] RED_DITHER_SIGNAL = new int[D1D2];
    final int[] GREEN_DITHER_SIGNAL = new int[D1D2];
    final int[] BLUE_DITHER_SIGNAL = new int[D1D2];
    for (int i = 0; i < D1D2; i++)
    {
      RED_DITHER_SIGNAL[i] = (2 * DITHER_MATRIX[i] - D1D2 + 1) * RED_SPACE / (2 * D1D2);
      GREEN_DITHER_SIGNAL[i] = (2 * DITHER_MATRIX[i] - D1D2 + 1) * GREEN_SPACE / (2 * D1D2);
      BLUE_DITHER_SIGNAL[i] = (2 * DITHER_MATRIX[i] - D1D2 + 1) * BLUE_SPACE / (2 * D1D2);
    }
    final int HEIGHT = in.getHeight();
    final int WIDTH = in.getWidth();
    int rowOffset = 0;
    for (int y = 0; y < HEIGHT; y++)
    {
      int offset = rowOffset;
      final int MAX_OFFSET = rowOffset + D1;
      for (int x = 0; x < WIDTH; x++)
      {
        // RED
        int redSample = in.getSample(INDEX_RED, x, y);
        redSample = redSample + RED_DITHER_SIGNAL[ offset ];
        if (redSample < 0)
        {
          redSample = 0;
        }
        else
        if (redSample > 255)
        {
          redSample = 255;
        }
        redSample >>= RED_SHIFT;
        out.putSample(RGBIndex.INDEX_RED, x, y, redSample * 255 / MAX_RED);
        // GREEN
        int greenSample = in.getSample(INDEX_GREEN, x, y);
        greenSample = greenSample + GREEN_DITHER_SIGNAL[ offset ];
        if (greenSample < 0)
        {
          greenSample = 0;
        }
        else
        if (greenSample > 255)
        {
          greenSample = 255;
        }
        greenSample >>= GREEN_SHIFT;
        out.putSample(RGBIndex.INDEX_GREEN, x, y, greenSample * 255 / MAX_GREEN);
        // BLUE
        int blueSample = in.getSample(INDEX_BLUE, x, y);
        blueSample = blueSample + BLUE_DITHER_SIGNAL[offset];
        if (blueSample < 0)
        {
          blueSample = 0;
        }
        else
        if (blueSample > 255)
        {
          blueSample = 255;
        }
        blueSample >>= BLUE_SHIFT;
        out.putSample(RGBIndex.INDEX_BLUE, x, y, blueSample * 255 / MAX_BLUE);
        //out.putSample(0, x, y, upq.mapToIndex(redSample, greenSample, blueSample));
        offset++;
        if (offset == MAX_OFFSET)
        {
          offset = rowOffset;
        }
      }
      rowOffset += D1;
      if (rowOffset >= DITHER_MATRIX.length)
      {
        rowOffset = 0;
      }
      setProgress(y, HEIGHT);
    }
  }

  public void process() throws
    MissingParameterException,
    WrongParameterException
  {
    ensureInputImageIsAvailable();
    ensureImagesHaveSameResolution();
    PixelImage in = getInputImage();
    PixelImage out = getOutputImage();
    if (in instanceof RGB24Image)
    {
      int sum = redBits + greenBits + blueBits;
      if (sum > 8)
      {
        process((RGB24Image)in, (RGB24Image)out);
      }
      else
      {
        process((RGB24Image)in, (Paletted8Image)out);
      }
    }
    else
    if (grayBits == 1)
    {
      process((Gray8Image)in, (BilevelImage)out);
    }
    else
    if (grayBits >= 2 && grayBits <= 7)
    {
      process((Gray8Image)in, (Gray8Image)out);
    }
  }

  public void setOutputBits(int bits)
  {
    if (bits >= 1 && bits <= 7)
    {
      grayBits = bits;
    }
    else
    {
      throw new IllegalArgumentException("Grayscale output bits must be from 1..7; got " + bits);
    }
  }

  /**
   * Sets the number of bits to be used for each RGB component in the output image.
   * Each argument must be one or larger.
   * The values defined by this method are only used if the input image implements
   * RGBIntegerImage.
   * Later, in {@link #process}, these values are checked against
   * the actual number of bits per component in the input image.
   * If any of the arguments of this method is equal to or larger
   * than those actual bits per channel values, a WrongParameterException
   * will then be thrown.
   * Right now, there is no way how this can be checked, because
   * the input image may not have been defined yet.
   * @param red number of bits for the red channel in the output image
   * @param green number of bits for the green channel in the output image
   * @param blue number of bits for the blue channel in the output image
   * @throws IllegalArgumentException if at least one argument is smaller than <code>1</code>
   */
  public void setRgbBits(int red, int green, int blue)
  {
    if (red > 0 && green > 0 && blue > 0)
    {
      redBits = red;
      greenBits = green;
      blueBits = blue;
    }
    else
    {
      throw new IllegalArgumentException("All parameters must be 1 or larger.");
    }
  }

  /**
   * Calls {@link #setThresholdValues} with a 16 x 16 matrix.
   */
  public void setStandardThresholdValues()
  {
    final int[] VALUES =
    {
       0,192, 48,240, 12,204, 60,2523,195, 51,243, 15,207, 63,255,
      128, 64,176,112,140, 76,188,124,131, 67,179,115,143, 79,191,127,
        32,224, 16,208, 44,236, 28,220, 35,227, 19,211, 47,239, 31,223,
      160, 96,144, 80,172,108,156, 92,163, 99,147, 83,175,111,159, 95,
          8,200, 56,2484,196, 52,244, 11,203, 59,2517,199, 55,247,
      136, 72,184,120,132, 68,180,116,139, 75,187,123,135, 71,183,119,
        40,232, 24,216, 36,228, 20,212, 43,235, 27,219, 39,231, 23,215,
      168,104,152, 88,164,100,148, 84,171,107,155, 91,167,103,151, 87,
          2,194, 50,242, 14,206, 62,2541,193, 49,241, 13,205, 61,253,
      130, 66,178,114,142, 78,190,126,129, 65,177,113,141, 77,189,125,
        34,226, 18,210, 46,238, 30,222, 33,225, 17,209, 45,237, 29,221,
     162, 98,146, 82,174,110,158, 94,161, 97,145, 81,173,109,157, 93,
        10,202, 58,2506,198, 54,2469,201, 57,2495,197, 53,245,
      138, 74,186,122,134, 70,182,118,137, 73,185,121,133, 69,181,117,
        42,234, 26,218, 38,230, 22,214, 41,233, 25,217, 37,229, 21,213,
      170,106,154, 90,166,102,150, 86,169,105,153, 89,165,101,149, 85
    };
    setThresholdValues(VALUES, 16, 16);
  }

  /**
   * Defines a matrix of threshold values that will be used for grayscale
   * dithering.
   * @param values the int values to use for comparing
   * @param valueWidth
   * @param valueHeight
   */
  public void setThresholdValues(int[] values, int valueWidth, int valueHeight)
  {
    if (values == null)
    {
      throw new IllegalArgumentException("The value array must be non-null.");
    }
    if (valueWidth < 1)
    {
      throw new IllegalArgumentException("The width argument must be at least 1.");
    }
    if (valueHeight < 1)
    {
      throw new IllegalArgumentException("The height argument must be at least 1.");
    }
    this.values = values;
    this.valueWidth = valueWidth;
    this.valueHeight = valueHeight;
    if (this.valueHeight * this.valueWidth < this.values.length)
    {
      throw new IllegalArgumentException("The array must have at least valuesWidth * valuesHeight elements..");
    }
  }
}
TOP

Related Classes of net.sourceforge.jiu.color.dithering.OrderedDither

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.