/*
 * $RCSfile: ArbROIMaskGenerator.java,v $
 * $Revision: 1.1 $
 * $Date: 2005-02-11 05:02:22 $
 * $State: Exp $
 *
 * Class:                   ArbROIMaskGenerator
 *
 * Description:             Generates masks when only rectangular ROIs exist
 *
 *
 *
 * COPYRIGHT:
 *
 * This software module was originally developed by Raphaël Grosbois and
 * Diego Santa Cruz (Swiss Federal Institute of Technology-EPFL); Joel
 * Askelöf (Ericsson Radio Systems AB); and Bertrand Berthelot, David
 * Bouchard, Félix Henry, Gerard Mozelle and Patrice Onno (Canon Research
 * Centre France S.A) in the course of development of the JPEG2000
 * standard as specified by ISO/IEC 15444 (JPEG 2000 Standard). This
 * software module is an implementation of a part of the JPEG 2000
 * Standard. Swiss Federal Institute of Technology-EPFL, Ericsson Radio
 * Systems AB and Canon Research Centre France S.A (collectively JJ2000
 * Partners) agree not to assert against ISO/IEC and users of the JPEG
 * 2000 Standard (Users) any of their rights under the copyright, not
 * including other intellectual property rights, for this software module
 * with respect to the usage by ISO/IEC and Users of this software module
 * or modifications thereof for use in hardware or software products
 * claiming conformance to the JPEG 2000 Standard. Those intending to use
 * this software module in hardware or software products are advised that
 * their use may infringe existing patents. The original developers of
 * this software module, JJ2000 Partners and ISO/IEC assume no liability
 * for use of this software module or modifications thereof. No license
 * or right to this software module is granted for non JPEG 2000 Standard
 * conforming products. JJ2000 Partners have full right to use this
 * software module for his/her own purpose, assign or donate this
 * software module to any third party and to inhibit third parties from
 * using this software module for non JPEG 2000 Standard conforming
 * products. This copyright notice must be included in all copies or
 * derivative works of this software module.
 *
 * Copyright (c) 1999/2000 JJ2000 Partners.
 * */
package jj2000.j2k.roi.encoder;

import jj2000.j2k.quantization.quantizer.*;
import jj2000.j2k.codestream.writer.*;
import jj2000.j2k.wavelet.analysis.*;
import jj2000.j2k.quantization.*;
import jj2000.j2k.image.input.*;
import jj2000.j2k.wavelet.*;
import jj2000.j2k.image.*;
import jj2000.j2k.util.*;
import jj2000.j2k.roi.*;

/**
 * This class generates the ROI bit-mask when, at least, one ROI is not
 * rectangular. In this case, the fast ROI bit-mask algorithm generation can
 * not be used.
 *
 * <P>The values are calculated from the scaling factors of the ROIs. The
 * values with which to scale are equal to u-umin where umin is the lowest
 * scaling factor within the block. The umin value is sent to the entropy
 * coder to be used for scaling the distortion values.
 *
 * @see ROIMaskGenerator
 *
 * @see ArbROIMaskGenerator
 * */
public class ArbROIMaskGenerator extends ROIMaskGenerator{

    /** The source of quantized wavelet transform coefficients */
    private Quantizer src;

    /** The ROI mask for the current tile for all components*/
    private int[][] roiMask;

    /** The low frequency part of a mask line */
    private int[] maskLineLow;

    /** The High frequency part of a mask line */
    private int[] maskLineHigh;

    /** A line or column of the mask with padding  */
    private int[] paddedMaskLine;

    /** Flag indicating if any ROI was found to be in this tile */
    private boolean roiInTile;

    /**
     * The constructor of the arbitrary mask generator
     *
     * @param rois The ROI info.
     *
     * @param nrc The number of components
     *
     * @param src The quantizer module
     * */
    public ArbROIMaskGenerator(ROI[] rois, int nrc, Quantizer src){
        super(rois,nrc);
        roiMask=new int[nrc][];
        this.src = src;
    }

    /**
     * This functions gets a DataBlk the size of the current code-block an
     * fills this block with the ROI mask.
     *
     * <P> In order to get the mask for a particular Subband, the subband tree
     * is traversed and at each decomposition, the ROI masks are computed.
     *
     * <P> The widths of the synthesis filters corresponding to the wavelet
     * filters used in the wavelet transform are used to expand the ROI masks
     * in the decompositions.
     *
     * @param db The data block that is to be filled with the mask
     *
     * @param sb The root of the subband tree to which db belongs
     *
     * @param magbits The max number of magnitude bits in any code-block
     *
     * @param c The number of the component
     *
     * @return Whether or not a mask was needed for this tile
     **/
    public boolean getROIMask(DataBlkInt db, Subband sb, int magbits, int c){
        int x = db.ulx;
        int y = db.uly;
        int w = db.w;
        int h = db.h;
        int tilew = sb.w;
        int tileh = sb.h;
        int[] maskData= (int[])db.getData();
        int i, j, k, bi, wrap;

        // If the ROI mask has not been calculated for this tile and
        // component, do so now.
        if(!tileMaskMade[c]){
            makeMask(sb,magbits,c);
            tileMaskMade[c]=true;
        }
        if(!roiInTile)
            return false;

        int[] mask = roiMask[c]; // local copy

        // Copy relevant part of the ROI mask to the datablock
        i=(y+h-1)*tilew+x+w-1;
        bi=w*h-1;
        wrap=tilew-w;
        for(j=h ; j>0 ; j--){
            for(k=w ; k>0 ; k--, i--, bi--){
                maskData[bi]=mask[i];
            }
            i-=wrap;
        }
        return true;

    }

    /**
     * This function returns the relevant data of the mask generator
     * */
    public String toString(){
        return("Fast rectangular ROI mask generator");
    }

    /**
     * This function generates the ROI mask for one tile-component.
     *
     * <P> Once the mask is generated in the pixel domain. it is decomposed
     * following the same decomposition scheme as the wavelet transform.
     *
     * @param sb The root of the subband tree used in the decomposition
     *
     * @param magbits The max number of magnitude bits in any code-block
     *
     * @param c component number
     */
    public void makeMask(Subband sb, int magbits, int c){
        int mask[]; // local copy
        ROI rois[] = this.rois;  // local copy
        int i,j,k,r,mink,minj,maxj;
        int lrx,lry;
        int x,y,w,h;
        int cx,cy,rad;
        int wrap;
        int curScalVal;
        int tileulx = sb.ulcx;
        int tileuly = sb.ulcy;
        int tilew = sb.w;
        int tileh = sb.h;
        int lineLen = (tilew>tileh) ? tilew : tileh;

        // Make sure there is a sufficiently large mask buffer
        if(roiMask[c] == null || ( roiMask[c].length < (tilew*tileh ))){
            roiMask[c] = new int[tilew*tileh];
            mask = roiMask[c];
        }
        else{
            mask = roiMask[c];
            for(i=tilew*tileh-1; i>=0; i--)
                mask[i] = 0;
        }

        // Make sure there are sufficiently large line buffers
        if(maskLineLow == null || (maskLineLow.length < (lineLen+1)/2))
            maskLineLow = new int[(lineLen+1)/2];
        if(maskLineHigh == null || (maskLineHigh.length < (lineLen+1)/2))
            maskLineHigh = new int[(lineLen+1)/2];

        roiInTile = false;
        // Generate ROIs in pixel domain:
        for(r=rois.length-1; r>=0; r--) {
            if(rois[r].comp == c) {
		curScalVal = magbits;

                if (rois[r].arbShape) {
                    ImgReaderPGM maskPGM = rois[r].maskPGM; // Local copy

                    if( (src.getImgWidth() != maskPGM.getImgWidth()) ||
                        (src.getImgHeight() != maskPGM.getImgHeight()) )
                        throw new IllegalArgumentException("Input image and"+
                                                           " ROI mask must "+
                                                           "have the same "+
                                                           "size");
                    x = src.getImgULX();
                    y = src.getImgULY();
                    lrx = x+src.getImgWidth()-1;
                    lry = y+src.getImgHeight()-1;
                    if( (x>tileulx+tilew) || (y>tileuly+tileh) ||
                        (lrx<tileulx) || (lry<tileuly) ) // Roi not in tile
                        continue;

                    // Check bounds
                    x   -= tileulx;
                    lrx -= tileulx;
                    y   -= tileuly;
                    lry -= tileuly;

                    int offx = 0;
                    int offy = 0;
                    if(x<0) {
                        offx = -x;
                        x = 0;
                    }
                    if(y<0) {
                        offy = -y;
                        y = 0;
                    }
                    w = (lrx > (tilew-1))? tilew-x:lrx+1-x;
                    h = (lry > (tileh-1))? tileh-y:lry+1-y;


                    // Get shape line by line to reduce memory
                    DataBlkInt srcblk = new DataBlkInt();
                    int mDcOff = -ImgReaderPGM.DC_OFFSET;
                    int nROIcoeff = 0;
                    int[] src_data;
                    srcblk.ulx = offx;
                    srcblk.w = w;
                    srcblk.h = 1;

                    i = (y+h-1)*tilew+x+w-1;
                    maxj = w;
                    wrap = tilew-maxj;
                    for(k=h; k>0; k--){
                        srcblk.uly = offy+k-1;
                        srcblk = (DataBlkInt)maskPGM.
                            getInternCompData(srcblk,0);
                        src_data = srcblk.getDataInt();

                        for(j=maxj; j>0; j--,i--){
                            if(src_data[j-1] != mDcOff) {
                                mask[i] = curScalVal;
                                nROIcoeff++;
                            }
                        }
                        i -= wrap;
                    }

                    if(nROIcoeff != 0) {
                        roiInTile = true;
                    }
                }
                else if(rois[r].rect){ // Rectangular ROI
                    x = rois[r].ulx;
                    y = rois[r].uly;
                    lrx = rois[r].w+x-1;
                    lry = rois[r].h+y-1;

                    if( (x>tileulx+tilew) || (y>tileuly+tileh) ||
                        (lrx<tileulx) || (lry<tileuly) ) // Roi not in tile
                        continue;

                    roiInTile=true;

                    // Check bounds
                    x   -= tileulx;
                    lrx -= tileulx;
                    y   -= tileuly;
                    lry -= tileuly;

                    x = (x<0) ? 0:x;
                    y = (y<0) ? 0:y;
                    w = (lrx > (tilew-1))? tilew-x:lrx+1-x;
                    h = (lry > (tileh-1))? tileh-y:lry+1-y;

                    i = (y+h-1)*tilew+x+w-1;
                    maxj = w;
                    wrap = tilew-maxj;
                    for(k=h; k>0; k--){
                        for(j=maxj; j>0; j--,i--){
			    mask[i] = curScalVal;
                        }
                        i -= wrap;
                    }
                }
                else{ // Non-rectangular ROI. So far only circular case
                    cx = rois[r].x-tileulx;
                    cy = rois[r].y-tileuly;
                    rad = rois[r].r;
                    i = tileh*tilew-1;
                    for(k=tileh-1; k>=0; k--){
                        for(j=tilew-1; j>=0; j--,i--){
                            if(((j-cx)*(j-cx)+(k-cy)*(k-cy) < rad*rad)){
                                mask[i] = curScalVal;
                                roiInTile = true;
                            }
                        }
                    }
                }
            }
        }

        // If wavelet transform is used
        if(sb.isNode) {
            // Decompose the mask according to the subband tree
            // Calculate size of padded line buffer
            WaveletFilter vFilter = sb.getVerWFilter();
            WaveletFilter hFilter = sb.getHorWFilter();
            int lvsup =
                vFilter.getSynLowNegSupport()+vFilter.getSynLowPosSupport();
            int hvsup =
                vFilter.getSynHighNegSupport()+vFilter.getSynHighPosSupport();
            int lhsup =
                hFilter.getSynLowNegSupport()+hFilter.getSynLowPosSupport();
            int hhsup =
                hFilter.getSynHighNegSupport()+hFilter.getSynHighPosSupport();
            lvsup = (lvsup>hvsup)? lvsup:hvsup;
            lhsup = (lhsup>hhsup)? lhsup:hhsup;
            lvsup = (lvsup>lhsup)? lvsup:lhsup;
            paddedMaskLine = new int[lineLen+lvsup];

            if(roiInTile)
                decomp(sb,tilew,tileh,c);
        }
    }

    /**
     * This function decomposes the mask for a node in the subband tree.
     * after the mask is decomposed for a node, this function is called for
     * the children of the subband. The decomposition is done line by line
     * and column by column
     *
     * @param sb The subband that is to be used for the decomposition
     *
     * @param tilew The width of the current tile
     *
     * @param tileh The height of the current tile
     *
     * @param c component number
     */
    private void decomp(Subband sb, int tilew, int tileh, int c){
        int ulx = sb.ulx;
        int uly = sb.uly;
        int w = sb.w;
        int h = sb.h;
        int scalVal,maxVal = 0;
        int i,j,k,s,hi,mi = 0,pin,li;
        int hmax,lmax,smax;
        int wrap,lineoffs,lastlow;
        int[] mask = roiMask[c]; // local copy
        int[] low = maskLineLow; // local copy
        int[] high = maskLineHigh; // local copy
        int[] padLine = paddedMaskLine; // local copy
        int highFirst = 0;
        int lastpin;


        if(!sb.isNode)
            return;

        // HORIZONTAL DECOMPOSITION

        // Calculate number of high and low samples after decomposition
        // and get support for low and high filters
        WaveletFilter filter = sb.getHorWFilter();
        int lnSup = filter.getSynLowNegSupport();
        int hnSup = filter.getSynHighNegSupport();
        int lpSup = filter.getSynLowPosSupport();
        int hpSup = filter.getSynHighPosSupport();
        int lsup = lnSup+lpSup+1;
        int hsup = hnSup+hpSup+1;

        // Calculate number of high/low coeffis in subbands
        highFirst = sb.ulcx%2;
        if(sb.w%2==0){
            lmax = w/2-1;
            hmax = lmax;
        }
        else{
            if(highFirst==0){
                lmax = (w+1)/2-1;
                hmax = w/2-1;
            }
            else{
                hmax = (w+1)/2-1;
                lmax = w/2-1;
            }
        }

        int maxnSup = (lnSup>hnSup) ? lnSup:hnSup; // Maximum negative support
        int maxpSup = (lpSup>hpSup) ? lpSup:hpSup; // Maximum positive support


        // Set padding to 0
        for(pin=maxnSup-1;pin>=0;pin--)
            padLine[pin] = 0;
        for(pin=maxnSup+w-1+maxpSup;pin>=w;pin--)
            padLine[pin] = 0;

        // Do decomposition of all lines
        lineoffs = (uly+h)*tilew+ulx+w-1;
        for(j=h-1;j>=0;j--){
            lineoffs -= tilew;
            // Get the line to transform from the mask
            mi=lineoffs;
            for(k=w, pin=w-1+maxnSup ; k>0 ; k--,mi--,pin--){
                padLine[pin] = mask[mi];
            }

            lastpin = maxnSup+highFirst+2*lmax+lpSup;
            for(k=lmax; k>=0 ; k--,lastpin-=2){ // Low frequency samples
                pin = lastpin;
                for(s=lsup;s>0;s--,pin--){
                    scalVal = padLine[pin];
                    if(scalVal>maxVal)
                        maxVal = scalVal;
                }
                low[k] = maxVal;
                maxVal = 0;
            }
            lastpin = maxnSup-highFirst+2*hmax+1+hpSup;
            for(k=hmax; k>=0 ; k--,lastpin-=2){ // High frequency samples
                pin = lastpin;
                for(s=hsup;s>0;s--,pin--){
                    scalVal = padLine[pin];
                    if(scalVal>maxVal)
                        maxVal = scalVal;
                }
                high[k] = maxVal;
                maxVal = 0;
            }
            // Put the lows and highs back
            mi=lineoffs;
            for(k=hmax; k>=0; k--,mi--){
                mask[mi] = high[k];
            }
            for(k=lmax;k>=0;k--,mi--){
                mask[mi] = low[k];
            }
        }

        // VERTICAL DECOMPOSITION

        // Calculate number of high and low samples after decomposition
        // and get support for low and high filters
        filter = sb.getVerWFilter();
        lnSup = filter.getSynLowNegSupport();
        hnSup = filter.getSynHighNegSupport();
        lpSup = filter.getSynLowPosSupport();
        hpSup = filter.getSynHighPosSupport();
        lsup = lnSup+lpSup+1;
        hsup = hnSup+hpSup+1;

        // Calculate number of high/low coeffs in subbands
        highFirst = sb.ulcy%2;
        if(sb.h%2==0){
            lmax = h/2-1;
            hmax = lmax;
        }
        else{
            if(sb.ulcy%2==0){
                lmax = (h+1)/2-1;
                hmax = h/2-1;
            }
            else{
                hmax = (h+1)/2-1;
                lmax = h/2-1;
            }
        }

        maxnSup = (lnSup>hnSup) ? lnSup:hnSup; // Maximum negative support
        maxpSup = (lpSup>hpSup) ? lpSup:hpSup; // Maximum positive support

        // Set padding to 0
        for(pin=maxnSup-1;pin>=0;pin--)
            padLine[pin] = 0;
        for(pin=maxnSup+h-1+maxpSup;pin>=h;pin--)
            padLine[pin] = 0;

        // Do decomposition of all columns
        lineoffs=(uly+h-1)*tilew+ulx+w;
        for(j=w-1;j>=0;j--){
            lineoffs--;
            // Get the line to transform from the mask
            mi = lineoffs;
            for(k=h, pin=k-1+maxnSup ; k>0 ; k--,mi-=tilew,pin--){
                padLine[pin] = mask[mi];
            }
            lastpin=maxnSup+highFirst+2*lmax+lpSup;
            for(k=lmax; k>=0 ; k--,lastpin-=2){ // Low frequency samples
                pin = lastpin;
                for(s=lsup;s>0;s--,pin--){
                    scalVal = padLine[pin];
                    if(scalVal>maxVal)
                        maxVal = scalVal;
                }
                low[k] = maxVal;
                maxVal = 0;
            }
            lastpin = maxnSup-highFirst+2*hmax+1+hpSup;
            for(k=hmax; k>=0 ; k--,lastpin-=2){ // High frequency samples
                pin = lastpin;
                for(s=hsup;s>0;s--,pin--){
                    scalVal = padLine[pin];
                    if(scalVal>maxVal)
                        maxVal = scalVal;
                }
                high[k] = maxVal;
                maxVal = 0;
            }
            // Put the lows and highs back
            mi=lineoffs;
            for(k=hmax;k>=0;k--,mi-=tilew){
                mask[mi] = high[k];
            }
            for(k=lmax;k>=0;k--,mi-=tilew){
                mask[mi] = low[k];
            }
        }

        if(sb.isNode){
            decomp(sb.getHH(), tilew, tileh, c);
            decomp(sb.getLH(), tilew, tileh, c);
            decomp(sb.getHL(), tilew, tileh, c);
            decomp(sb.getLL(), tilew, tileh, c);
        }

    }
}





