Package jjil.algorithm

Source Code of jjil.algorithm.Gray8AffineWarp

/*
* Gray8AffineWarp.java
*
* Created on September 9, 2006, 3:17 PM
*
* Copyright 2007 by Jon A. Webb
*     This program is free software: you can redistribute it and/or modify
*    it under the terms of the GNU Lesser General Public License as published by
*    the Free Software Foundation, either version 3 of the License, or
*    (at your option) any later version.
*
*    This program is distributed in the hope that it will be useful,
*    but WITHOUT ANY WARRANTY; without even the implied warranty of
*    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
*    GNU Lesser General Public License for more details.
*
*    You should have received a copy of the Lesser GNU General Public License
*    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*
*/

package jjil.algorithm;
import jjil.core.Error;
import jjil.core.Gray8Image;
import jjil.core.Gray8OffsetImage;
import jjil.core.Image;
import jjil.core.PipelineStage;
import jjil.core.Vec2;

/**
* This PipelineStage performs an affine transformation on an input
* @author webb
*/
public class Gray8AffineWarp extends PipelineStage {  
    static final int WARP_X_FIRST = 1;
    static final int WARP_Y_FIRST = 2;
   
    int nMaxX, nMaxY, nMinX, nMinY;
    private int nWarpOrder; // either WARP_X_FIRST or WARP_Y_FIRST
    int nXOffset, nYOffset;
    int rnWarp[][];
    int rnWarpX[];
    int rnWarpY[];
   
    /** Creates a new instance of Gray8AffineWarp. Gray8AffineWarp performs
     * an affine warp on an input Gray8Image. The affine transformation is
     * decomposed into two stages, following the work of George Wolberg.
     * See http://www-cs.ccny.cuny.edu/~wolberg/diw.html for the definitive
     * work on image warping.
     * <p>
     * @param warp the 2x3 affine warp to be performed. The elements of this
     * matrix are assumed to be scaled by 2**16 for accuracy.
     * @throws jjil.core.Error if the warp is null or not a 2x3 matrix.
     */
    public Gray8AffineWarp(int[][] warp) throws jjil.core.Error {
        this.setWarp(warp);
    }
   
   
    /**
     * Calculate the affine transformation applied to p, keeping in mind
     * that the warp is scaled by 2**16, so we must shift to get the
     * correct result
     * @param a 2x3 affine transformation, scaled by 2**16
     * @param p input vector
     * @return transformed vector
     */
    private Vec2 affineTrans(int a[][], Vec2 p) {
        return new Vec2(
                (a[0][0] * p.getX() + a[0][1] * p.getY() + a[0][2])>>16,
                (a[1][0] * p.getX() + a[1][1] * p.getY() + a[1][2])>>16);
    }

    /**
     * Affine warp of an image.
     *
     * @param image the input gray image.
     * @throws jjil.core.Error if the input image is not gray,
     * or the trapezoid already specified extends outside its bounds.
     */
    public void push(Image image) throws jjil.core.Error {
        if (!(image instanceof Gray8Image)) {
            throw new Error(
            Error.PACKAGE.ALGORITHM,
            ErrorCodes.IMAGE_NOT_GRAY8IMAGE,
            image.toString(),
            null,
            null);
        }
        // first calculate bounds of output image
        Vec2 p00 = affineTrans(this.rnWarp, new Vec2(0, 0));
        Vec2 p01 = affineTrans(this.rnWarp, new Vec2(0, image.getHeight()));
        Vec2 p10 = affineTrans(this.rnWarp, new Vec2(image.getWidth(), 0));
        Vec2 p11 = affineTrans(this.rnWarp, new Vec2(image.getWidth(),
                 image.getHeight()));
        this.nMinX = (int) Math.min(p00.getX(),
                Math.min(p01.getX(), Math.min(p10.getX(), p11.getX())));
        this.nMaxX = (int) Math.max(p00.getX(),
                Math.max(p01.getX(), Math.max(p10.getX(), p11.getX())));
        this.nMinY = (int) Math.min(p00.getY(),
                Math.min(p01.getY(), Math.min(p10.getY(), p11.getY())));
        this.nMaxY = (int) Math.max(p00.getY(),
                Math.max(p01.getY(), Math.max(p10.getY(), p11.getY())));
        this.nXOffset = -this.nMinX;
        this.nYOffset = -this.nMinY;
        if (this.nWarpOrder == Gray8AffineWarp.WARP_X_FIRST) {
            Gray8Image grayX = warpX((Gray8Image) image);
            //super.setOutput(new Gray8OffsetImage(grayX, this.nXOffset, this.nYOffset));
            Gray8Image grayY = warpY(grayX);
            super.setOutput(new Gray8OffsetImage(grayY, this.nXOffset, this.nYOffset));
        } else {
            Gray8Image grayY = warpY((Gray8Image) image);
            //super.setOutput(new Gray8OffsetImage(grayY, this.nXOffset, this.nYOffset));
            Gray8Image grayX = warpX(grayY);
            super.setOutput(new Gray8OffsetImage(grayX, this.nXOffset, this.nYOffset));
        }
    }
   
    /** Sets the warp in use and decomposes it into two stages, determining
     * the order of the warp (x first or y first).
     * @param warp the 2 x 3 affine warp transformation. The matrix is
     * assumed to have been scaled by 2**16 for accuracy.
     * @throws jjil.core.Error if the warp is not 2x3 or the warp is not
     * decomposable (warp[0][0] or warp[1][1] is 0).
     */
    public void setWarp(int[][] warp) throws jjil.core.Error {
        if (warp.length != 2 || warp[0].length != 3 || warp[1].length != 3) {
            throw new Error(
                            Error.PACKAGE.ALGORITHM,
                            jjil.algorithm.ErrorCodes.PARAMETER_WRONG_SIZE,
                            warp.toString(),
                            null,
                            null);
        }
        // nDivisor is scaled by 2**16. Since warp is scaled by 2**16 multiplying
        // its elements would scale by 2**32, resulting in overflow. So we
        // rescale before multiplying
        int nDivisorY = ((warp[0][0]>>8) * (warp[1][1]>>8)) -
                ((warp[0][1]>>8) * (warp[1][0]>>8));
        if (warp[0][0] == 0 || warp[1][1] == 0 || nDivisorY == 0) {
            throw new Error(
                            Error.PACKAGE.ALGORITHM,
                            jjil.algorithm.ErrorCodes.PARAMETER_OUT_OF_RANGE,
                            warp.toString(),
                            null,
                            null);
        }
        this.rnWarp = warp;
        if (Math.abs(warp[0][0]) > Math.abs(warp[1][1])) {
            // warp in x direction first
            this.nWarpOrder = Gray8AffineWarp.WARP_X_FIRST;
            // copy x warp
            // rnWarp is scaled by 2**16, as is warp. If we divided warp
            // by nDivisor, scaled by 2**16 we'd scale by 2**8 so we must rescale
            // carefully to get the output scaled properly while not overflowing
            // the warp values (except column 2)
            // are all < 1 so scaling by 2**16 gives a number
            // less than 2**16 which means it can be safely scaled up 8 bits
            this.rnWarpX = new int[3];
            this.rnWarpX[0] = (warp[1][1]<<8) / (nDivisorY>>8);
            this.rnWarpX[1] = -(warp[0][1]<<8) / (nDivisorY>>8);
            this.rnWarpX[2] = ((warp[0][1]>>4)*(warp[1][2]>>4) -
                    (warp[0][2]>>4)*(warp[1][1]>>4))/(nDivisorY>>8);
            // calculate y warp
            this.rnWarpY = new int[3];
            this.rnWarpY[0] = -(warp[1][0]<<8)/(warp[1][1]>>8);
            this.rnWarpY[1] = (1<<24) / (warp[1][1]>>8);
            this.rnWarpY[2] = -(warp[1][2]<<8) / (warp[1][1]>>8);
        } else {
            // warp in y direction first
            this.nWarpOrder = Gray8AffineWarp.WARP_Y_FIRST;
            // copy y warp
            this.rnWarpY = new int[3];
            this.rnWarpY[0] = -(warp[1][0]<<8) / (nDivisorY>>8);
            this.rnWarpY[1] = (warp[0][0]<<8) / (nDivisorY>>8);
            this.rnWarpY[2] = ((warp[0][2]>>4)*(warp[1][0]>>4) -
                    (warp[0][0]>>4)*(warp[1][2]>>4))/(nDivisorY>>8);
            // calculate x warp
            this.rnWarpX = new int[3];
            this.rnWarpX[0] = (1<<24) / (warp[0][0]>>8);
            this.rnWarpX[1] = -(warp[0][1]<<8) / (warp[0][0]>>8);
            this.rnWarpX[2] = -(warp[0][2]<<8) / (warp[0][0]>>8);
        }
    }
   
    public Vec2 warpVec(Vec2 p) {
        int x = (p.getX() * this.rnWarp[0][0] + p.getY() * this.rnWarp[0][1] +
                this.rnWarp[0][2])>>16;
        int y = (p.getX() * this.rnWarp[1][0] + p.getY() * this.rnWarp[1][1] +
                this.rnWarp[1][2])>>16;
        return new Vec2(x,y);
    }

    private Gray8Image warpX(Gray8Image grayIn) {
        // allocate image. it is implicitly offset by nMinX
        Gray8Image grayOut = new Gray8Image(
                nMaxX - nMinX,
                grayIn.getHeight(),
                Byte.MIN_VALUE);
        // pointer to input
        byte[] bDataIn = grayIn.getData();
        byte[] bDataOut = grayOut.getData();
        for (int x = nMinX; x<nMaxX; x++) {
            for (int y = 0; y<grayIn.getHeight(); y++) {
                // calculate x in original image.
                // nX is scaled by 2**16.
                // y does not change but is offset by nYOffset
                int nX = x * this.rnWarpX[0] +
                        this.rnWarpX[1] * (y + this.nYOffset) +
                        this.rnWarpX[2];
                // nXfloor is the integer value of nX, unscaled
                int nXfloor = nX >> 16;
                // nXfrace is the fractional component of nX, scaled by 2**16
                int nXfrac = nX - (nXfloor<<16);
                // interpolate to get point
                if (nXfloor >= 0 && nXfloor < grayIn.getWidth()-1) {
                    int bIn = bDataIn[y*grayIn.getWidth() + nXfloor];
                    int bInP1 = bDataIn[y*grayIn.getWidth() + nXfloor + 1];
                    int bOut = (bIn * ((1<<16)- nXfrac) +
                                bInP1 * nXfrac)>>16;
                    bDataOut[grayOut.getWidth()*y + x-nMinX] = (byte) bOut;
                }
            }
        }
        this.nXOffset = nMinX;
        return grayOut;
    }

    private Gray8Image warpY(Gray8Image grayIn) {
        // allocate image. it is implicitly offset by nMinY
        Gray8Image grayOut = new Gray8Image(
                grayIn.getWidth(),
                nMaxY - nMinY,
                Byte.MIN_VALUE);
        // pointer to input
        byte[] bDataIn = grayIn.getData();
        byte[] bDataOut = grayOut.getData();
        for (int y = nMinY; y<nMaxY; y++) {
            for (int x = 0; x<grayIn.getWidth(); x++) {
                // calculate y in original image
                // x does not change
                // nY is scaled by 2**16
                int nY = this.rnWarpY[0] * (x + this.nXOffset) +
                        this.rnWarpY[1] * y +
                        this.rnWarpY[2];
                // nYfloor is the integer portion of nY, unscaled
                int nYfloor = nY >> 16;
                // nYfrac is the fractional portion of nY, scaled by 2**16
                int nYfrac = nY - (nYfloor<<16);
                // interpolate to get point
                if (nYfloor >= 0 && nYfloor < grayIn.getHeight()-1) {
                    int bIn = bDataIn[nYfloor*grayIn.getWidth() + x];
                    int bInP1 = bDataIn[(nYfloor+1)*grayIn.getWidth() + x];
                    int bOut =  (bIn * ((1<<16) - nYfrac) +
                                 bInP1 * nYfrac)>>16;
                    bDataOut[grayOut.getWidth()*(y-nMinY) + x] = (byte) bOut;
                }
            }
        }
        this.nYOffset = nMinY;
        return grayOut;
    }
   
    /**
     * Returns a string describing the current instance. All the constructor
     * parameters are returned in the order specified in the constructor.
     * @return The string describing the current instance. The string is of the form
     * "jjil.algorithm.Gray8AffineWarpxxx (startRow,endRow,leftColStart,
     * rightColStart,leftColEnd,rightColEnd)"
     */
    public String toString() {
        return super.toString() + " (" + this.rnWarp.toString() + ")"; //$NON-NLS-1$
    }
}
TOP

Related Classes of jjil.algorithm.Gray8AffineWarp

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.