Intelligent Resize

File

Normal resizing of an image to anything but the original aspect ratio introduces distortion. This is an implementation of a classic method of "intelligent" resizing which takes into account the structures in the image and tries to conserve them when scaling up or down.

Intelligent_Resize.ijm

cw=getWidth();
ch=getHeight();

Dialog.create("Intelligent Resize");
Dialog.addNumber("Width:", cw, 0, 5, "px");
Dialog.addNumber("Height:", ch, 0, 5, "px");
Dialog.addMessage("");
Dialog.addNumber("Change:", 1, 0, 5, "px");
Dialog.addNumber("Energy range:", 1, 0, 5, "px");
Dialog.addNumber("Search range:", 7, 0, 5, "px");
Dialog.addNumber("Restreach weight: 1/", 64, 0, 5, "");
Dialog.show();

tw=Dialog.getNumber;
th=Dialog.getNumber;
change=Dialog.getNumber;
energyrange=Dialog.getNumber;
searchrange=Dialog.getNumber;
restreachval=Dialog.getNumber;

ow=maxOf(cw, tw);
oh=maxOf(ch, th);

xiterations=floor(abs(cw-tw)/change)+1;
yiterations=floor(abs(ch-th)/change)+1;
xchangerem=(abs(cw-tw))%change;
ychangerem=(abs(ch-th))%change;

if (tw<=cw) {
    xchange=-change;
    xchangerem=-xchangerem;
} else {
    xchange=change;
    xchangerem=xchangerem;
}
if (th<=ch) {
    ychange=-change;
    ychangerem=-ychangerem;
} else {
    ychange=change;
    ychangerem=ychangerem;
}

colour=newArray(ow*oh);
for (x=0; x<cw; x++) {
    for (y=0; y<ch; y++) {
        colour[x+y*ow]=getPixel(x,y);
    }
}

energy=newArray(ow*oh);
for (x=0; x<cw; x++) {
    showProgress(x/cw);
    showStatus("Calculating start energy");
    for (y=0; y<ch; y++) {
        energy[x+y*ow]=energyOf(x, y, ow, oh, energyrange, colour);
    }
}

restretchpen=3*255*pow(energyrange*2+1, 2)/restreachval; //Energy penalty given to a pixel once streched once.

//Do two iterations, once for x contraction, once for y contraction
for (j=0; j<2; j++) {
    if (j==0) {
        //treat as x,y
        iterations=xiterations;
        change=xchange;
        changerem=xchangerem;
    } else if (j==1) {
        //treat as y,x
        colour=transpose2DArray(ow, oh, colour);
        energy=transpose2DArray(ow, oh, energy);
        iterations=yiterations;
        change=ychange;
        changerem=ychangerem;
        tow=oh;
        toh=ow;
        tcw=ch;
        tch=cw;
        ow=tow;
        oh=toh;
        cw=tcw;
        ch=tch;
    }
    for (i=0; i<iterations; i++) {
        showProgress(i/iterations);
        showStatus("Iterations: set "+j+1+" of 2, iteration "+i+1+" of "+iterations);
        xres=newArray(ch);
        for (y=0; y<ch; y++) {
            //Calculates the x value for that y
            if (y==0) {
                xres[y]=floor(random()*cw);
            } else {
                testvalues=newArray(searchrange*2+1);
                for (a=-searchrange; a<=searchrange; a++) {
                    xa=xres[y-1]+a;
                    if (xa>=0 && xa<cw) {
                        testvalues[a+searchrange]=energy[xa+y*ow];
                    } else {
                        testvalues[a+searchrange]=3*255*pow(energyrange*2+1, 2);
                    }
                }
                min=minOfArray(testvalues);
                avals=occurencesInArray(min, testvalues);
                xres[y]=xres[y-1]+avals[floor(random()*lengthOf(avals))]-searchrange;
            }
        }
        if (i!=iterations-1) {
            change=change;
        } else {
            change=changerem;
        }
        cw=cw+change;
        //Rejigs the colour array to add/subtract pixels at the x values
        if (change<0) {
            //If shrinking the image
            for (y=0; y<ch; y++) {
                for (x=xres[y]; x<cw; x++) {
                    colour[x+y*ow]=colour[(x-change)+y*ow];
                }
            }
        } else {
            //If growing the image
            for (y=0; y<ch; y++) {
                for (x=cw-1; x>xres[y]; x--) {
                    colour[x+y*ow]=colour[(x-1)+y*ow];
                }
            }
        }
        //Recalculates the energy array
        if (change<0) {
            //If shrinking the image
            //Rejigs the array and recalculates the energy array for pixels within range of the changed pixel
            for (y=0; y<ch; y++) {
                for (x=xres[y]; x<cw; x++) {
                    energy[x+y*ow]=energy[(x-change)+y*ow];
                }
            }
            for (y=0; y<ch; y++) {
                for (a=-energyrange; a<=energyrange; a++) {
                    xa=xres[y]+a;
                    if (xa>=0 && xa<cw) {
                        energy[xa+y*ow]=energyOf(xa, y, ow, oh, energyrange, colour);
                    }
                }
            }
        } else {
            //If growing the image
            //Rejigs the array and sets the duplicated pixels to a high energy to prevent mass stretching
            for (y=0; y<ch; y++) {
                for (x=cw-1; x>xres[y]; x--) {
                    energy[x+y*ow]=energy[(x-1)+y*ow];
                }
                for (x=xres[y]; x<=xres[y]+change; x++) {
                    energy[x+y*ow]=energy[x+y*ow]+restretchpen;
                }
            }
        }
    }
    if (j==1) {
        //revert to x,y
        tow=oh;
        toh=ow;
        tcw=ch;
        tch=cw;
        ow=tow;
        oh=toh;
        cw=tcw;
        ch=tch;
        colour=transpose2DArray(oh, ow, colour);
        energy=transpose2DArray(oh, ow, energy);
    }
}
    
newImage("Result", "RGB color Black", tw, th, 1);
for (x=0; x<tw; x++) {
    for (y=0; y<th; y++) {
        setPixel(x, y, colour[x+y*ow]);
    }
}

//Min of array function
//Returns the minimum value in the array
function minOfArray(array) {
    max=0;
    for (a=0; a<lengthOf(array); a++) {
        max=maxOf(max, array[a]);
    }
    min=max;
    for (a=0; a<lengthOf(array); a++) {
        min=minOf(min, array[a]);
    }
    return min;
}

//Occurrences in array function
//Returns an array containing the indices of where the value occurs in the input array
function occurencesInArray(value, array) {
    count=0;
    for (a=0; a<lengthOf(array); a++) {
        if (array[a]==value) {
            count++;
        }
    }
    indices=newArray(count);
    index=0;
    for (a=0; a<lengthOf(array); a++) {
        if (array[a]==value) {
            indices[index]=a;
            index++;
        }
    }
    return indices;
}

//Transpose array function
//Transposes a 2D array, ie. swaps x and y
function transpose2DArray(w, h, array) {
    transposed=newArray(h*w);
    for (x=0; x<w; x++) {
        for (y=0; y<h; y++) {
            transposed[y+x*h]=array[x+y*w];
        }
    }
    return transposed;
}

//Energy function
//Calculates the energy of an x, y location given a colour array and a search range
function energyOf(x, y, w, h, energyrange, colourarray) {
    red=(colourarray[x+y*w]>>16)&0xff;
    gre=(colourarray[x+y*w]>>8)&0xff;
    blu=(colourarray[x+y*w]>>0)&0xff;
    sumdif=0;
    count=0;
    for (a=-energyrange; a<=energyrange; a++) {
        for (b=-energyrange; b<=energyrange; b++) {
            xa=x+a;
            yb=y+b;
            if (xa>=0 && xa<w && yb>=0 && yb<h) {
                rdif=abs((colourarray[xa+yb*w]>>16)&0xff-red);
                gdif=abs((colourarray[xa+yb*w]>>8)&0xff-gre);
                bdif=abs((colourarray[xa+yb*w]>>0)&0xff-blu);
                sumdif=sumdif+rdif+gdif+bdif;
                count++;
            }
        }
    }
    return sumdif;///count;
}

ImageJ Macros Gallery