Package com.lightcrafts.media.jai.opimage

Source Code of com.lightcrafts.media.jai.opimage.SubsampleBinaryToGrayOpImage

/*
* $RCSfile: SubsampleBinaryToGrayOpImage.java,v $
*
* Copyright (c) 2005 Sun Microsystems, Inc. All rights reserved.
*
* Use is subject to license terms.
*
* $Revision: 1.1 $
* $Date: 2005/02/11 04:56:44 $
* $State: Exp $
*/
package com.lightcrafts.media.jai.opimage;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.geom.Point2D;
import java.awt.image.Raster;
import java.awt.image.RenderedImage;
import java.awt.image.WritableRaster;
import java.awt.image.ColorModel;
import java.awt.image.IndexColorModel;
import java.awt.image.DataBuffer;
import java.awt.image.DataBufferByte;
import java.awt.image.SampleModel;
import java.awt.image.PixelInterleavedSampleModel;
import java.awt.image.SinglePixelPackedSampleModel;

import com.lightcrafts.media.jai.util.ImageUtil;
import com.lightcrafts.media.jai.util.JDKWorkarounds;
import com.lightcrafts.mediax.jai.ImageLayout;
import java.util.Map;
import com.lightcrafts.mediax.jai.GeometricOpImage;
import com.lightcrafts.mediax.jai.JAI;
import com.lightcrafts.mediax.jai.PackedImageData;
import com.lightcrafts.mediax.jai.PixelAccessor;

/**
* A class extending <code>GeometricOpImage</code> to
* subsample binary images to gray scale images.  Image scaling operations
* require rectilinear backwards mapping and padding by the resampling
* filter dimensions.
*
* <p> When applying scale factors of scaleX, scaleY to a source image
* with width of src_width and height of src_height, the resulting image
* is defined to have the following bounds:
*
* <code></pre>
*       dst minX  = floor(src minX  * scaleX)
*       dst minY  = floor(src minY  * scaleY)
*       dst width  =  floor(src width  * scaleX)
*       dst height =  floor(src height * scaleY)
* </pre></code>
*
* @see ScaleOpImage
*
*/
public class SubsampleBinaryToGrayOpImage extends GeometricOpImage {

    /** The horizontal scale factor. */
    protected float scaleX;

    /** The vertical scale factor. */
    protected float scaleY;

    /** Cached value equal to 1/scaleX. */
    protected float invScaleX;

    /** Cached value equal to 1/scaleY. */
    protected float invScaleY;

    /** Used to determine whether a float is close to an int */
    private float floatTol;

    /** same as ceil(invScaleX), ceil(invScaleY) */
    private int blockX;
    private int blockY;

    /** destination image width */
    private int dWidth;
    /** destination image height*/
    private int dHeight;


    /** the 1st pixel location for destination pixels, i.e.,
     *  the source pixel matrix
     *  Note the index runs from 0..dstWidth-1 and 0..dstHeight-1
     *       [yValues[j] yValues[j]+blockY-1] by [xValues[i] xValues[i]+blockX-1]
     *  will be condensed to form pixel <code>i</code>th pixel in row <code>j</code>
     */
    private int[] xValues;
    private int[] yValues;

    // a look up table; lut[i] counts 1s in binary expression of i
    private int[] lut = new int[256];

    /**
     * Convert from number of bits on count to gray value, with
     * scaling, i.e. if invScaleX,Y=3,3, then the possible bit
     * counts are 0..9, hence the lookup tables are [0..9] * 255/9.
     */
    protected byte[] lutGray;

    // package accessible for SubsampleBinaryToGrayOpImage4x4, etc...
    static ImageLayout layoutHelper(RenderedImage source,
            float scaleX,
            float scaleY,
            ImageLayout il,
                                    Map config) {

        ImageLayout layout = (il == null) ?
            new ImageLayout() : (ImageLayout)il.clone();

  // to compute dWidth and dHeight
  // fTol and dWi, dHi must be the same as in computeDestInfo(..)
  // due to static method, a few lines of coding are repeated
  int srcWidth = source.getWidth();
  int srcHeight= source.getHeight();

  float f_dw = scaleX * srcWidth;
  float f_dh = scaleY * srcHeight;
  float fTol = .1F * Math.min(scaleX/(f_dw+1.0F),scaleY/(f_dh+1.0F));

  int dWi = (int)(f_dw);
  int dHi = (int)(f_dh);

  // let it be int in the almost int case
  //   espacially in the true int case with float calculation errors
  if(Math.abs(Math.round(f_dw)-f_dw) < fTol){
      dWi = Math.round(f_dw);
  }

  if(Math.abs(Math.round(f_dh)-f_dh) < fTol){
      dHi= Math.round(f_dh);
  }

  // Set the top left coordinate of the destination
  layout.setMinX((int)(scaleX * source.getMinX()));
  layout.setMinY((int)(scaleY * source.getMinY()));

  layout.setWidth(dWi);
  layout.setHeight(dHi);

  // sample model
  SampleModel sm = layout.getSampleModel(null);

  if (sm == null ||
      sm.getDataType() != DataBuffer.TYPE_BYTE ||
      !(sm instanceof PixelInterleavedSampleModel  ||
        sm instanceof SinglePixelPackedSampleModel &&
              sm.getNumBands()==1)){

      // Width and height will be corrected in OpImage.layoutHelper
            sm = new PixelInterleavedSampleModel(DataBuffer.TYPE_BYTE,
             1,
             1,
             1,
             1,
             new int[] {0});
  }

        layout.setSampleModel(sm);

  ColorModel cm = layout.getColorModel(null);

        if(cm == null ||
           !JDKWorkarounds.areCompatibleDataModels(sm, cm)) {

            layout.setColorModel(ImageUtil.getCompatibleColorModel(sm,
                                                                   config));
        }

        return layout;
    }

    // Since this operation deals with packed bits in a binary image, we
    // do not need to expand the IndexColorModel
    private static Map configHelper(Map configuration) {

  Map config;

  if (configuration == null) {
      config = new RenderingHints(JAI.KEY_REPLACE_INDEX_COLOR_MODEL,
          Boolean.FALSE);
  } else {

      config = configuration;

      if (!config.containsKey(JAI.KEY_REPLACE_INDEX_COLOR_MODEL)) {
    RenderingHints hints = (RenderingHints)configuration;
    config = (RenderingHints)hints.clone();
    config.put(JAI.KEY_REPLACE_INDEX_COLOR_MODEL, Boolean.FALSE);
      }
  }

  return config;
    }

    /**
     * Constructs a <code>SubsampleBinaryToGrayOpImage</code>
     * from a <code>RenderedImage</code> source, x and y scale
     * object.  The image dimensions are determined by forward-mapping
     * the source bounds, and are passed to the superclass constructor
     * by means of the <code>layout</code> parameter.  Other fields of
     * the layout are passed through unchanged.  If
     * <code>layout</code> is <code>null</code>, a new
     * <code>ImageLayout</code> will be constructor to hold the bounds
     * information.
     *
     * The float rounding errors, such as 1.2 being
     * internally represented as 1.200001, are dealt with
     * the floatTol, which is set up so that only 1/10 of pixel
     * error will occur at the end of a line, which yields correct
     * results with Math.round() operation.
     * The repeatability is guaranteed with a one-time computed
     * table xvalues and yvalues.
     *
     * @param layout an <code>ImageLayout</code> optionally containing
     *        the tile grid layout, <code>SampleModel</code>, and
     *        <code>ColorModel</code>, or <code>null</code>.
     * @param source a <code>RenderedImage</code>.
     *        from this <code>OpImage</code>, or <code>null</code>.  If
     *        <code>null</code>, no caching will be performed.
     * @param cobbleSources a boolean indicating whether
     *        <code>computeRect</code> expects contiguous sources.
     * @param extender a <code>BorderExtender</code>, or <code>null</code>.
     * @param interp an <code>Interpolation</code> object to use for
     *        resampling.
     * @param scaleX scale factor along x axis.
     * @param scaleY scale factor along y axis.
     *
     * @throws IllegalArgumentException if combining the
     *         source bounds with the layout parameter results in negative
     *         output width or height.
     */
    public SubsampleBinaryToGrayOpImage(RenderedImage source,
          ImageLayout layout,
          Map config,
          float scaleX,
          float scaleY){

        super(vectorize(source),
              layoutHelper(source, scaleX, scaleY, layout, config),
              configHelper(config),
              true, // cobbleSources,
        null, // extender
              null,  // interpolation
        null);

  this.scaleX = scaleX;
  this.scaleY = scaleY;
  int srcMinX = source.getMinX();
  int srcMinY = source.getMinY();
  int srcWidth = source.getWidth();
  int srcHeight= source.getHeight();

  // compute floatTol, invScaleX, blockX, dWidth, dHeight,...
  computeDestInfo(srcWidth, srcHeight);

  if (extender == null) {
      computableBounds = new Rectangle(0, 0, dWidth, dHeight);
  } else {
      // If extender is present we can write the entire destination.
      computableBounds = getBounds();
  }


  // these can be delayed, such as placed in computeRect()
  buildLookupTables();

  // compute the begining bit position of each row and column
  computeXYValues(srcWidth, srcHeight, srcMinX, srcMinY);
    }

    /**
     * Computes the source point corresponding to the supplied point.
     *
     * @param destPt the position in destination image coordinates
     * to map to source image coordinates.
     *
     * @return a <code>Point2D</code> of the same class as
     * <code>destPt</code>.
     *
     * @throws IllegalArgumentException if <code>destPt</code> is
     * <code>null</code>.
     *
     * @since JAI 1.1.2
     */
    public Point2D mapDestPoint(Point2D destPt) {
        if (destPt == null) {
            throw new IllegalArgumentException(JaiI18N.getString("Generic0"));
        }

        Point2D pt = (Point2D)destPt.clone();

        pt.setLocation(destPt.getX()/scaleX, destPt.getY()/scaleY);

        return pt;
    }

    /**
     * Computes the destination point corresponding to the supplied point.
     *
     * @param sourcePt the position in source image coordinates
     * to map to destination image coordinates.
     *
     * @return a <code>Point2D</code> of the same class as
     * <code>sourcePt</code>.
     *
     * @throws IllegalArgumentException if <code>sourcePt</code> is
     * <code>null</code>.
     *
     * @since JAI 1.1.2
     */
    public Point2D mapSourcePoint(Point2D sourcePt) {
        if (sourcePt == null) {
            throw new IllegalArgumentException(JaiI18N.getString("Generic0"));
        }

        Point2D pt = (Point2D)sourcePt.clone();

        pt.setLocation(sourcePt.getX()*scaleX, sourcePt.getY()*scaleY);

        return pt;
    }

    /**
     * Returns the minimum bounding box of the region of the destination
     * to which a particular <code>Rectangle</code> of the specified source
     * will be mapped.
     *
     * @param sourceRect the <code>Rectangle</code> in source coordinates.
     * @param sourceIndex the index of the source image.
     *
     * @return a <code>Rectangle</code> indicating the destination
     *         bounding box, or <code>null</code> if the bounding box
     *         is unknown.
     *
     * @throws IllegalArgumentException if <code>sourceIndex</code> is
     *         negative or greater than the index of the last source.
     * @throws IllegalArgumentException if <code>sourceRect</code> is
     *         <code>null</code>.
     */
    protected Rectangle forwardMapRect(Rectangle sourceRect,
                                       int sourceIndex) {

        if ( sourceRect == null ) {
            throw new IllegalArgumentException(JaiI18N.getString("Generic0"));
        }

        if (sourceIndex != 0) {
            throw new IllegalArgumentException(JaiI18N.getString("Generic1"));
        }

  // Get the source dimensions
  int x0 = sourceRect.x - blockX +1;
  int y0 = sourceRect.y - blockY +1;
  x0 = x0 < 0? 0: x0;
  y0 = y0 < 0? 0: y0;

  int dx0 = (int)(x0 * scaleX);
  int dy0 = (int)(y0 * scaleY);
  while (xValues[dx0] > x0 && dx0 > 0){
    dx0--;
  }
  while(yValues[dy0] > y0 && dy0 > 0){
    dy0--;
  }

  int x1 = sourceRect.x + sourceRect.width - 1;
  int y1 = sourceRect.y + sourceRect.height- 1;

  int dx1 = (int)Math.round(x1 * scaleX);
  int dy1 = (int)Math.round(y1 * scaleY);
  dx1 = dx1 >= dWidth ? dWidth -1 : dx1;
  dy1 = dy1 >= dHeight? dHeight-1 : dy1;
  while (xValues[dx1] < x1 && dx1 < dWidth -1){
    dx1++;
  }
  while (yValues[dy1] < y1 && dy1 < dHeight-1){
    dy1++;
  }

        dx0 += this.minX;
        dy0 += this.minY;
        dx1 += this.minX;
        dy1 += this.minY;

        // Return the writable destination area
  return new Rectangle(dx0, dy0, dx1-dx0+1, dy1-dy0+1);
    }

    /**
     * Returns the minimum bounding box of the region of the specified
     * source to which a particular <code>Rectangle</code> of the
     * destination will be mapped.
     *
     * @param destRect the <code>Rectangle</code> in destination coordinates.
     * @param sourceIndex the index of the source image.
     *
     * @return a <code>Rectangle</code> indicating the source bounding box,
     *         or <code>null</code> if the bounding box is unknown.
     *
     * @throws IllegalArgumentException if <code>sourceIndex</code> is
     *         negative or greater than the index of the last source.
     * @throws IllegalArgumentException if <code>destRect</code> is
     *         <code>null</code>.
     */
    protected Rectangle backwardMapRect(Rectangle destRect,
                                        int sourceIndex) {

        if ( destRect == null ) {
            throw new IllegalArgumentException(JaiI18N.getString("Generic0"));
        }

        if (sourceIndex != 0) {
            throw new IllegalArgumentException(JaiI18N.getString("Generic1"));
        }

        // Get the destination rectangle coordinates and dimensions
  // Note: indices starting from 0, thus minX/Y should be considered
        int sx0 = xValues[destRect.x - this.minX];
        int sy0 = yValues[destRect.y - this.minY];
        int sx1 = xValues[destRect.x - this.minX + destRect.width -1];
        int sy1 = yValues[destRect.y - this.minY + destRect.height-1];

  return new Rectangle(sx0, sy0, sx1 - sx0 + blockX, sy1 - sy0 + blockY);
    }


    /**
     * Performs a subsamplebinarytogray operation on a specified rectangle.
     * The sources are cobbled.
     *
     * @param sources  an array of source Rasters, guaranteed to provide all
     *                 necessary source data for computing the output.
     * @param dest     a WritableRaster  containing the area to be computed.
     * @param destRect the rectangle within dest to be processed.
     */
    protected void computeRect(Raster[]   sources,
                               WritableRaster dest,
                               Rectangle  destRect) {
  Raster source = sources[0];

        switch (source.getSampleModel().getDataType()) {
        case DataBuffer.TYPE_BYTE:
        case DataBuffer.TYPE_SHORT:
        case DataBuffer.TYPE_USHORT:
        case DataBuffer.TYPE_INT:
            byteLoop(source, dest, destRect);
      break;
        default:
            throw new
    RuntimeException(JaiI18N.getString("SubsampleBinaryToGrayOpImage0"));
        }
    }

    private void byteLoop(Raster source, WritableRaster dest, Rectangle  destRect){
        PixelAccessor pa = new PixelAccessor(source.getSampleModel(), null);
  PackedImageData pid =
            pa.getPackedPixels(source, source.getBounds(), false, false);
  byte[] sourceData   = pid.data;
        int sourceDBOffset  = pid.offset;
  int dx  = destRect.x;
  int dy  = destRect.y;
  int dwi = destRect.width;
  int dhi = destRect.height;
        int sourceTransX = pid.rect.x;   // source.getSampleModelTranslateX();
        int sourceTransY = pid.rect.y;   // source.getSampleModelTranslateY();

        PixelInterleavedSampleModel destSM =
            (PixelInterleavedSampleModel)dest.getSampleModel();
        DataBufferByte destDB =
            (DataBufferByte)dest.getDataBuffer();
        int destTransX = dest.getSampleModelTranslateX();
        int destTransY = dest.getSampleModelTranslateY();
        int destScanlineStride = destSM.getScanlineStride();

        byte[] destData  = destDB.getData();
        int destDBOffset = destDB.getOffset();

        int[] sbytenum = new int[dwi];
        int[] sstartbit= new int[dwi];

  int[] sAreaBitsOn   = new int[dwi];
        for (int i = 0; i < dwi; i++) {
            int x = xValues[dx+i-this.minX];
            int sbitnum = pid.bitOffset + (x - sourceTransX);
            sbytenum[i] = sbitnum >> 3;
            sstartbit[i]   = sbitnum % 8;
        }

        for(int j = 0; j < dhi; j++) {

     for(int i=0; i < dwi; i++){
        sAreaBitsOn[i] = 0;
     }

     for(int y = yValues[dy+j-this.minY]; y < yValues[dy+j-this.minY]+blockY; y++){

              int sourceYOffset =
                (y - sourceTransY)*pid.lineStride + sourceDBOffset;

        int  delement=0, selement, sendbiti, sendbytenumi;
        for(int i=0; i < dwi; i++){
           delement = 0;
     sendbiti     = sstartbit[i] + blockX - 1;
     sendbytenumi = sbytenum[i]+(sendbiti>>3);
     sendbiti    %= 8;

     selement = 0x00ff & (int)sourceData[sourceYOffset + sbytenum[i]];

     if(sbytenum[i]==sendbytenumi){
        selement  <<= 24 + sstartbit[i];
        selement >>>= 31 - sendbiti + sstartbit[i];
        delement += lut[selement];
     }else{
        selement  <<= 24 + sstartbit[i];
        selement >>>= 24;
        delement += lut[selement];
        for(int b=sbytenum[i]+1; b< sendbytenumi; b++){
           selement  = 0x00ff & (int)sourceData[sourceYOffset + b];
           delement += lut[selement];
        }

        selement = 0x00ff & (int)sourceData[sourceYOffset + sendbytenumi];
        selement >>>= 7-sendbiti;
        delement += lut[selement];
     }
     sAreaBitsOn[i] += delement;
        }
     }
     int destYOffset =
         (j + dy - destTransY)*destScanlineStride + destDBOffset;

     destYOffset += dx - destTransX;

     // update dest values for row j in raster
     for(int i = 0; i < dwi; i++){
         destData[destYOffset + i] = lutGray[sAreaBitsOn[i]];
     }
  }
    }


    private void computeDestInfo(int srcWidth, int srcHeight){

  // Inverse scale factors
  invScaleX = 1.0F / scaleX;
  invScaleY = 1.0F / scaleY;
  blockX = (int)Math.ceil(invScaleX);
  blockY = (int)Math.ceil(invScaleY);

  // calculate dst width and height
  float f_dw = scaleX * srcWidth;
  float f_dh = scaleY * srcHeight;
  floatTol = .1F * Math.min(scaleX/(f_dw+1.0F),scaleY/(f_dh+1.0F));

  dWidth = (int)(f_dw);
  dHeight= (int)(f_dh);

  // let it be int in the almost int case
  //   espacially in the true int case with float calculation errors
  if (Math.abs(Math.round(f_dw)-f_dw) < floatTol){
     dWidth = Math.round(f_dw);
  }

  if (Math.abs(Math.round(f_dh)-f_dh) < floatTol){
     dHeight= Math.round(f_dh);
  }

  if (Math.abs(Math.round(invScaleX) - invScaleX) < floatTol){
     invScaleX = Math.round(invScaleX);
     blockX    = (int)invScaleX;
  }

  if (Math.abs(Math.round(invScaleY) - invScaleY) < floatTol){
     invScaleY = Math.round(invScaleY);
     blockY = (int)invScaleY;
  }

    }


    // buildLookupTables()
    // initializes variabes bitSet and lut
    // to be called mainly in the constructor
    private final void buildLookupTables(){
  // lut
  lut[0] = 0; lut[1] = 1; lut[2] = 1; lut[3] = 2;
  lut[4] = 1; lut[5] = 2; lut[6] = 2; lut[7] = 3;
  lut[8] = 1; lut[9] = 2; lut[10]= 2; lut[11]= 3;
  lut[12]= 2; lut[13]= 3; lut[14]= 3; lut[15]= 4;
  for(int i= 16; i < 256; i++){
     lut[i= lut[i&(0x0f)] + lut[(i>>4)&(0x0f)];
  }

  // lutGray
  if (lutGray != null) return;
  lutGray = new byte[blockX * blockY +1];
  for (int i=0; i < lutGray.length; i++){
      int tmp =  (int)Math.round(255.0F*i/(lutGray.length-1.0F));
      lutGray[i] = tmp>255? (byte)0xff : (byte)tmp;
  }

  // switch black-white if needed
  if (isMinWhite(this.getSourceImage(0).getColorModel()))
      for(int i=0; i < lutGray.length; i++)
         lutGray[i]= (byte)(255-(0xff &lutGray[i]));
    }

    // this function can be called
    // only after dWidth and dHeight has been set in the constructor
    // XY values should be computed and stored for repeatable behavior
    // taking care of non-zero minX, minY
    private void computeXYValues(int srcWidth, int srcHeight,
         int srcMinX,  int srcMinY){
      if (xValues==null || yValues == null){
   xValues = new int[dWidth];
   yValues = new int[dHeight];
      }

      float tmp;
      for(int i= 0; i < dWidth; i++){
          tmp = invScaleX * i;
    xValues[i] = (int)Math.round(tmp);
      }
      if (xValues[dWidth-1]+blockX > srcWidth){
   xValues[dWidth-1]--;
      }

      for(int i=0;  i < dHeight; i++){
     tmp = invScaleY * i;
   yValues[i] = Math.round(tmp);
      }
      if (yValues[dHeight-1] + blockY > srcHeight){
   yValues[dHeight-1]--;
      }

      // if case the source MinX/Y are not zeros
      if (srcMinX != 0)
    for (int i = 0; i < dWidth;  i++xValues[i] += srcMinX;
      if (srcMinY != 0)
    for (int i = 0; i < dHeight; i++yValues[i] += srcMinY;
    }

    // check to see whether an indexed colormodel is inverted
    // returns false if cm not IndexColorModel
    // red[0] = 0     returns false
    // red[0] = 255   returns true
    static boolean isMinWhite(ColorModel cm){
      if (cm == null || !(cm instanceof IndexColorModel)) return false;

      byte[] red = new byte[256];
      ((IndexColorModel)cm).getReds(red);
      return (red[0]==(byte)255?true:false);
    }
}
TOP

Related Classes of com.lightcrafts.media.jai.opimage.SubsampleBinaryToGrayOpImage

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.