Package net.sourceforge.jiu.color.dithering

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

/*
* ClusteredDotDither
*
* Copyright (c) 2002, 2003 Marco Schmidt.
* All rights reserved.
*/

package net.sourceforge.jiu.color.dithering;

import net.sourceforge.jiu.data.BilevelImage;
import net.sourceforge.jiu.data.GrayIntegerImage;
import net.sourceforge.jiu.data.MemoryBilevelImage;
import net.sourceforge.jiu.data.PixelImage;
import net.sourceforge.jiu.ops.ImageToImageOperation;
import net.sourceforge.jiu.ops.MissingParameterException;
import net.sourceforge.jiu.ops.WrongParameterException;
import net.sourceforge.jiu.util.ComparatorInterface;
import net.sourceforge.jiu.util.Sort;

/**
* Apply a clustered dot ordered dither to a grayscale image, converting
* it to a bilevel image in the process.
* Works with {@link net.sourceforge.jiu.data.GrayIntegerImage} objects
* as input and {@link net.sourceforge.jiu.data.BilevelImage} objects
* as output.
* Resolution of both must be the same.
* Use one of the predefined dither matrices or have one compiled from
* a <em>spot function</em> (given by an object of a class implementing
* {@link net.sourceforge.jiu.color.dithering.SpotFunction}).
* There are a couple of classes implementing that spot function interface
* in the dithering package.
* If no matrix is specified, {@link #process()} creates a default matrix
* by calling {@link #setOrder3DitherMatrix()}.
* <h3>Usage example</h3>
* <pre>
* ClusteredDotDither dither = new ClusteredDotDither();
* dither.setInputImage(image); // some GrayIntegerImage
* dither.setDitherMatrix(8, 8, new DiamondSpotFunction());
* dither.process();
* PixelImage ditheredImage = dither.getOutputImage();
* </pre>
*
* <h3>Credits</h3>
* The predefined dither matrices were taken from
* <a target="_top" href="http://netpbm.sourceforge.net">Netpbm</a>'s
* <code>pgmtopbm</code> program (the matrices are stored in the
* file <code>dithers.h</code>).
* <p>
* I learned about spot functions and their use from Austin Donnelly's
* GIMP plugin <em>newsprint</em> - it has extensive comments, and the
* <a target="_top" href="http://www.cl.cam.ac.uk/~and1000/newsprint/">newsprint website</a>
* explains the
* <a target="_top" href="http://www.cl.cam.ac.uk/~and1000/newsprint/clustered-dot.html">theoretical background</a>.
* @author Marco Schmidt
* @since 0.9.0
*/
public class ClusteredDotDither extends ImageToImageOperation
{
  private int ditherHeight;
  private int ditherWidth;
  private int[] ditherData;

  public void process() throws
    MissingParameterException,
    WrongParameterException
  {
    if (ditherData == null)
    {
      setDefaults();
    }
    ensureInputImageIsAvailable();
    PixelImage in = getInputImage();
    if (!(in instanceof GrayIntegerImage))
    {
      throw new WrongParameterException("Input image must implement GrayIntegerImage.");
    }
    PixelImage out = getOutputImage();
    if (out == null)
    {
      out = new MemoryBilevelImage(in.getWidth(), in.getHeight());
      setOutputImage(out);
    }
    else
    {
      if (!(out instanceof BilevelImage))
      {
        throw new WrongParameterException("Output image must implement BilevelImage.");
      }
      ensureOutputImageResolution(in.getWidth(), in.getHeight());
    }
    process((GrayIntegerImage)in, (BilevelImage)out);
  }

  private void process(GrayIntegerImage in, BilevelImage out)
  {
    // find maximum entry in dither matrix
    int maxTableValue = 1;
    for (int i = 0; i < ditherData.length; i++)
    {
      if (ditherData[i] > maxTableValue)
      {
        maxTableValue = ditherData[i];
      }
    }
    maxTableValue++;

    // create adjusted dither matrix data
    final int MAX_SAMPLE = in.getMaxSample(0) + 1;
    final int[] data = new int[ditherData.length];
    for (int i = 0; i < data.length; i++)
    {
      data[i] = ditherData[i] * MAX_SAMPLE / maxTableValue;
    }

    // do the actual dithering
    final int HEIGHT = in.getHeight();
    final int WIDTH = in.getWidth();
    for (int y = 0; y < HEIGHT; y++)
    {
      int ditherOffset = (y % ditherHeight) * ditherWidth;
      int samplesLeft = ditherWidth;
      for (int x = 0; x < WIDTH; x++)
      {
        if (in.getSample(0, x, y) >= data[ditherOffset++])
        {
          out.putWhite(x, y);
        }
        else
        {
          out.putBlack(x, y);
        }
        if (--samplesLeft == 0)
        {
          samplesLeft = ditherWidth;
          ditherOffset -= ditherWidth;
        }
      }
      setProgress(y, HEIGHT);
    }
  }

  private void setDefaults()
  {
    setOrder3DitherMatrix();
  }

  /**
   * Sets the dither matrix to be used in this operation.
   * @param width number of entries in horizontal direction
   * @param height number of entries in vertical direction
    * @param data matrix entries, in order top to bottom, and in each row left to right
    * @throws IllegalArgumentException if width or height are smaller than one or data
    *  is <code>null</code> or has not at least width times height entries
   */
  public void setDitherMatrix(int width, int height, int[] data)
  {
    if (width < 1)
    {
      throw new IllegalArgumentException("Width must be one or larger.");
    }
    if (height < 1)
    {
      throw new IllegalArgumentException("Height must be one or larger.");
    }
    if (data == null)
    {
      throw new IllegalArgumentException("Data must not be null.");
    }
    if (data.length < width * height)
    {
      throw new IllegalArgumentException("Data must have at least width times height entries.");
    }
    ditherWidth =  width;
    ditherHeight = height;
    ditherData = data;
  }

  /**
   * Creates and sets a dither matrix of user-defined size and
   * compiles it from a spot function.
   * Creates a matrix from the spot function and calls {@link #setDitherMatrix(int, int, int[])}.
   * @param width width of matrix, must be one or larger
   * @param height height of matrix, must be one or larger
   * @param f the spot function to be used for compiling the matrix
   */
  public void setDitherMatrix(int width, int height, SpotFunction f)
  {
    class MatrixElement implements ComparatorInterface
    {
      int index;
      double value;
      public int compare(Object o1, Object o2)
      {
        MatrixElement e1 = (MatrixElement)o1;
        MatrixElement e2 = (MatrixElement)o2;
        if (e1.value < e2.value)
        {
          return -1;
        }
        else
        if (e1.value == e2.value)
        {
          return 0;
        }
        else
        {
          return 1;
        }
      }
    }
    int[] data = new int[width * height];
    MatrixElement[] matrixElements = new MatrixElement[data.length];
    for (int i = 0; i < data.length;  i++)
    {
      matrixElements[i] = new MatrixElement();
      matrixElements[i].index = i;
    }
    int index = 0;
    for (int y = 0; y < height; y++)
    {
      for (int x = 0; x < width; x++)
      {
        double sx = ((double)x / (width - 1) - 0.5) * 2;
        double sy = ((double)y / (height - 1) - 0.5) * 2;
        double value = f.compute(sx, sy);
        if (value < -1.0)
        {
          value = -1.0;
        }
        else
        if (value > 1.0)
        {
          value = 1.0;
        }
        matrixElements[index++].value = value;
      }
    }
    boolean balanced = f.isBalanced();
    if (!balanced)
    {
      Sort.sort(matrixElements, matrixElements[0]);
    }
    for (int i = 0; i < data.length; i++)
    {
      MatrixElement elem = matrixElements[i];
      if (balanced)
      {
        data[elem.index] = (int)(elem.value * 254);
      }
      else
      {
        data[elem.index] = i * 255 / data.length;
      }
    }
    setDitherMatrix(width, height, data);
  }

  /**
   * Sets a 6 times 6 elements matrix to be used for dithering.
   */
  public void setOrder3DitherMatrix()
  {
    setDitherMatrix(6, 6, new int[]
      { 9, 11, 10867,
       12, 17, 16501,
       13, 14, 15432,
        8679, 11, 10,
        501, 12, 17, 16,
        432, 13, 14, 15});
  }

  /**
   * Sets an 8 times 8 elements matrix to be used for dithering.
   */
  public void setOrder4DitherMatrix()
  {
    setDitherMatrix(8, 8, new int[]
      {18,20,19,16,13,11,12,15,
      27,28,29,22, 4, 3, 2, 9,
      26,31,30,21, 5, 0, 1,10,
      23,25,24,17, 8, 6, 7,14,
      13,11,12,15,18,20,19,16,
       4, 3, 2, 9,27,28,29,22,
       5, 0, 1,10,26,31,30,21,
       8, 6, 7,14,23,25,24,17});
  }

  /**
   * Sets a 16 times 16 elements matrix to be used for dithering.
   */
  public void setOrder8DitherMatrix()
  {
    setDitherMatrix(16, 16, new int[] {
       64, 69, 77, 87, 86, 76, 68, 67, 63, 58, 50, 40, 41, 51, 59, 60,
       70, 94,100,109,108, 99, 93, 75, 57, 33, 27, 18, 19, 28, 34, 52,
       78,101,114,116,115,112, 98, 83, 49, 26, 13, 11, 12, 15, 29, 44,
       88,110,123,124,125,118,107, 85, 39, 174329, 20, 42,
       89,111,122,127,126,117,106, 84, 38, 16501, 10, 21, 43,
       79,102,119,121,120,113, 97, 82, 48, 25867, 14, 30, 45,
       71, 95,103,104,105, 96, 92, 74, 56, 32, 24, 23, 22, 31, 35, 53,
       65, 72, 80, 90, 91, 81, 73, 66, 62, 55, 47, 37, 36, 46, 54, 61,
       63, 58, 50, 40, 41, 51, 59, 60, 64, 69, 77, 87, 86, 76, 68, 67,
       57, 33, 27, 18, 19, 28, 34, 52, 70, 94,100,109,108, 99, 93, 75,
       49, 26, 13, 11, 12, 15, 29, 44, 78,101,114,116,115,112, 98, 83,
       39, 174329, 20, 42, 88,110,123,124,125,118,107, 85,
       38, 16501, 10, 21, 43, 89,111,122,127,126,117,106, 84,
       48, 25867, 14, 30, 45, 79,102,119,121,120,113, 97, 82,
       56, 32, 24, 23, 22, 31, 35, 53, 71, 95,103,104,105, 96, 92, 74,
        62, 55, 47, 37, 36, 46, 54, 61, 65, 72, 80, 90, 91, 81, 73, 66});
  }
}
TOP

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

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.