low level programmer

Main | Next page »
星期一 十月 06, 2008

JAI - 載圖轉 BufferedImage

JAI 載完圖是 PlanarImage, 可是一般我們處理的時候用 BufferedImage.
如果需要 JAI 幫忙載圖, 可是又想操作 BufferedImage 可以用下面的 method

private BufferedImage createBufferedImage() {
    RenderedImage input = JAI.create("fileload", "test.tif");
    BufferedImage bimg = new BufferedImage(input.getWidth(), input.getHeight(), BufferedImage.TYPE_INT_ARGB);
    ((Graphics2D) bimg.getGraphics()).drawRenderedImage(input, new AffineTransform());
    bimg.getGraphics().dispose();        
    return bimg;
}

星期六 九月 27, 2008

計算讓圖片能夠等比縮小

description

在 JFileChooser 預覽畫面的時候, 可以看到一個現象就是如果圖片超過預覽區的大小, 就會依照原圖比例縮小到預覽區容的下 size. 但如果原圖就比預覽區小的話就直接原圖秀出來即可. 另外 Graphics2D.drawImage 可以指定要把圖的寬高畫多少, 搭配起來只要算好圖的寬高應該多少就可以讓圖正確的呈現出來.

codes

以下這段程式就是讓你可以輸入原圖的寬高以及預覽區的寬高, 或你想限制的寬高, 由程式替你算一個等比例的 size.
比方說你有一張圖寬 1000px, 高 30px. 可是你比須放進寬高皆 200px 的範圍內, 那程式就會替你算出你的圖應該縮成寬 200px, 高 6px 就可以照原圖比例的縮小了.
這不是什麼好算法啦, 應該有更簡單的, 希望有更簡單算法的人能分享一下. 這個 method 只是拿來方便用.
public class TestFixedSize {

    public static void main(String[] args) {
        System.out.println(calFixedSize(new Dimension(200,200), new Dimension(1000,30)));
    }
    
    public static Dimension calFixedSize(Dimension fixedSize, Dimension originalSize) {
        int fixedWidth = fixedSize.width;
        int fixedHeight = fixedSize.height;
        int imgWidth = originalSize.width;
        int imgHeight = originalSize.height;
        int resultWidth = 0;
        int resultHeight = 0;
        if (fixedWidth > imgWidth && fixedHeight > imgHeight) {
            resultWidth = (int) imgWidth;
            resultHeight = (int) imgHeight;
        } else {
            if (imgWidth > imgHeight) {
                resultWidth = (int) fixedWidth;
                resultHeight = (int) (fixedWidth * imgHeight / imgWidth);
            } else {
                resultWidth = (int) (fixedHeight * imgWidth / imgHeight);
                resultHeight = (int) fixedHeight;
            }
        }
        return new Dimension(resultWidth, resultHeight);
    }
}

星期五 九月 12, 2008

JAI - 銳化效果 (ConvolveDescriptor)

description

用 convolve 實做銳化效果.
傳入不同的 float[] 可以看到不同的影像.

reference

ConvolveDescriptor

codes

public class JAITester {

    private RenderedImage doHistogram(RenderedImage image) {
        float[] sobelSharp = { 
            -1, -2, -1,
             0,  0,  0,
             1,  2,  1 
        };
        float[] prewittSharp = { 
            -1,-1,-1,
             0, 0, 0,
             1, 1, 1
        };
        float[] laplacienSharp = {
            -1,-1,-1,
            -1, 9,-1,
            -1,-1,-1
        };
        KernelJAI kernel = new KernelJAI(3,3,prewittSharp);
        RenderedImage img = JAI.create("convolve", image, kernel);
        return img;
    }
    
    /** 
     * http://forums.java.net/jive/message.jspa?messageID=221209
     * this gets rid of exception for not using native acceleration */
    static {
        System.setProperty("com.sun.media.jai.disableMediaLib", "true");
    }
    private static final String IMG_FILE_1 = "test2.jpg";

    public static void main(String[] args) {
        JAITester test = new JAITester();
        test.test();
    }

    private void test() {
        PlanarImage input = JAI.create("fileload", IMG_FILE_1);
        JPanel jPanelMain = new JPanel(new MigLayout("", "grow", "grow"));
        jPanelMain.add(new JScrollPane(new DisplayJAI(input)), "grow");
        jPanelMain.add(new JScrollPane(new DisplayJAI(doHistogram(input))), "grow");
        show(jPanelMain);
    }

    private void show(JComponent comp) {
        JFrame f = new JFrame();
        f.setLocation(50, 50);
        f.setPreferredSize(new Dimension(800, 600));
        f.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        f.getContentPane().add(comp);
        f.pack();
        f.setVisible(true);
    }
}          

星期二 九月 09, 2008

JAI - 中間值(最大值 最小值)濾波(Median Filter, MaxFilter, MinFilter)

description

Median Filter 就是用一個陣列來計算每個像素, 把像素值改為該陣列包含的值的中間值.
相較於 Median Filter, MaxFilter 就是取最大值, MinFilter 就是取最小值.
目的是保持大致上的影像減少雜訊.

reference

MedianFilterDescriptor
MaxFilterDescriptor
MinFilterDescriptor

codes

這個程式的效果是變模糊了.
public class JAITester {

    private RenderedImage doHistogram(RenderedImage imageToMedianFilter) {
        ParameterBlock mfParams = new ParameterBlock();
        mfParams.addSource(imageToMedianFilter);
        mfParams.add( MedianFilterDescriptor.MEDIAN_MASK_SQUARE );
        mfParams.add( Integer.valueOf("5") );
        imageToMedianFilter = JAI.create( "MedianFilter", mfParams, null );
        return imageToMedianFilter;
    }
    
    /** 
     * http://forums.java.net/jive/message.jspa?messageID=221209
     * this gets rid of exception for not using native acceleration */
    static {
        System.setProperty("com.sun.media.jai.disableMediaLib", "true");
    }
    private static final String IMG_FILE_1 = "test2.jpg";

    public static void main(String[] args) {
        JAITester test = new JAITester();
        test.test();
    }

    private void test() {
        PlanarImage input = JAI.create("fileload", IMG_FILE_1);
        JPanel jPanelMain = new JPanel(new MigLayout("", "grow", "grow"));
        jPanelMain.add(new JScrollPane(new DisplayJAI(input)), "grow");
        jPanelMain.add(new JScrollPane(new DisplayJAI(doHistogram(input))), "grow");
        show(jPanelMain);
    }

    private void show(JComponent comp) {
        JFrame f = new JFrame();
        f.setLocation(50, 50);
        f.setPreferredSize(new Dimension(800, 600));
        f.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        f.getContentPane().add(comp);
        f.pack();
        f.setVisible(true);
    }
}      
    
MaxFilter
public class JAITester {

    private RenderedImage doHistogram(RenderedImage imageToMaxFilter) {
        ParameterBlock mfParams = new ParameterBlock();
        mfParams.addSource(imageToMedianFilter);
        mfParams.add( MaxFilterDescriptor.MAX_MASK_SQUARE );
        mfParams.add( Integer.valueOf("5") );
        imageToMaxFilter = JAI.create( "MaxFilter", mfParams, null );
        return imageToMedianFilter;
    }
    
    /** 
     * http://forums.java.net/jive/message.jspa?messageID=221209
     * this gets rid of exception for not using native acceleration */
    static {
        System.setProperty("com.sun.media.jai.disableMediaLib", "true");
    }
    private static final String IMG_FILE_1 = "test2.jpg";

    public static void main(String[] args) {
        JAITester test = new JAITester();
        test.test();
    }

    private void test() {
        PlanarImage input = JAI.create("fileload", IMG_FILE_1);
        JPanel jPanelMain = new JPanel(new MigLayout("", "grow", "grow"));
        jPanelMain.add(new JScrollPane(new DisplayJAI(input)), "grow");
        jPanelMain.add(new JScrollPane(new DisplayJAI(doHistogram(input))), "grow");
        show(jPanelMain);
    }

    private void show(JComponent comp) {
        JFrame f = new JFrame();
        f.setLocation(50, 50);
        f.setPreferredSize(new Dimension(800, 600));
        f.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        f.getContentPane().add(comp);
        f.pack();
        f.setVisible(true);
    }
}      
    
MinFilter
public class JAITester {

    private RenderedImage doHistogram(RenderedImage imageToMedianFilter) {
        ParameterBlock mfParams = new ParameterBlock();
        mfParams.addSource(imageToMedianFilter);
        mfParams.add( MinFilterDescriptor.MIN_MASK_SQUARE  );
        mfParams.add( Integer.valueOf("5") );
        imageToMedianFilter = JAI.create( "MinFilter", mfParams, null );
        return imageToMedianFilter;
    }
    
    /** 
     * http://forums.java.net/jive/message.jspa?messageID=221209
     * this gets rid of exception for not using native acceleration */
    static {
        System.setProperty("com.sun.media.jai.disableMediaLib", "true");
    }
    private static final String IMG_FILE_1 = "test2.jpg";

    public static void main(String[] args) {
        JAITester test = new JAITester();
        test.test();
    }

    private void test() {
        PlanarImage input = JAI.create("fileload", IMG_FILE_1);
        JPanel jPanelMain = new JPanel(new MigLayout("", "grow", "grow"));
        jPanelMain.add(new JScrollPane(new DisplayJAI(input)), "grow");
        jPanelMain.add(new JScrollPane(new DisplayJAI(doHistogram(input))), "grow");
        show(jPanelMain);
    }

    private void show(JComponent comp) {
        JFrame f = new JFrame();
        f.setLocation(50, 50);
        f.setPreferredSize(new Dimension(800, 600));
        f.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        f.getContentPane().add(comp);
        f.pack();
        f.setVisible(true);
    }
}          

星期一 九月 08, 2008

JAI - 查表操作 (LookupDescriptor)

description

查表操作就是提供一個顏色陣列去比對什麼顏色查表後要變成什麼顏色.
例如想要讓顏色變亮的其中一種作法就是把 RGB 顏色都一起提昇.

reference

LookupDescriptor
LookupTableJAI

codes

這個程式就可以透過修改 brightLevelUp 的值修改圖片.
注意這個程式處理的是一個 band 的圖, 如果是多個 band 則 bright 必須變成二維陣列.
這裡有個 issue 就是超過 127 就會變負. 然後除了用 byte 以外的其他型態都會呈現錯誤, 不知道是為什麼
public class JAITester {

    private RenderedImage doHistogram(RenderedImage imageToHistogram) {
        imageToHistogram = Utils.toSingleBand(imageToHistogram);
        int brightLevelUp = 128;
        byte[] byteHBins = new byte[256];
        for ( int i = 0; i < byteHBins.length; i++ ) {
            if ( i + brightLevelUp > 255 ) {
                byteHBins[i] = (byte) 255;
            } else {
                byteHBins[i] = (byte) (i + brightLevelUp);
            }
        }
        LookupTableJAI lookupTableJAI = new LookupTableJAI( byteHBins );
        ParameterBlock lookupParams = new ParameterBlock();
        lookupParams.add( lookupTableJAI );
        RenderedImage newImage = JAI.create("lookup", imageToHistogram, lookupTableJAI, null);
        return newImage;
    }
    
    /** 
     * http://forums.java.net/jive/message.jspa?messageID=221209
     * this gets rid of exception for not using native acceleration */
    static {
        System.setProperty("com.sun.media.jai.disableMediaLib", "true");
    }
    private static final String IMG_FILE_1 = "test2.jpg";

    public static void main(String[] args) {
        JAITester test = new JAITester();
        test.test();
    }

    private void test() {
        PlanarImage input = JAI.create("fileload", IMG_FILE_1);
        JPanel jPanelMain = new JPanel(new MigLayout("", "grow", "grow"));
        jPanelMain.add(new JScrollPane(new DisplayJAI(input)), "grow");
        jPanelMain.add(new JScrollPane(new DisplayJAI(doHistogram(input))), "grow");
        show(jPanelMain);
    }

    private void show(JComponent comp) {
        JFrame f = new JFrame();
        f.setLocation(50, 50);
        f.setPreferredSize(new Dimension(800, 600));
        f.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        f.getContentPane().add(comp);
        f.pack();
        f.setVisible(true);
    }
}   

class Utils {
    public static RenderedImage toSingleBand(RenderedImage imageToBinarize) {
        ParameterBlock bandCombineParams = new ParameterBlock();
        bandCombineParams.addSource(imageToBinarize);
        bandCombineParams.add( new double[][]{ {0.2,0.3,0.4,0.5} } );
        return JAI.create("bandcombine", bandCombineParams, null);
    }
}

星期六 九月 06, 2008

JAI - RGB 轉 IHS 處理後轉 RGB. (影像辨識的類似操作)

description

這段程式的內容, 第一步先將 RGB 的圖轉成 IHS 的資料.
IHS 的資料有個特點就是顏色和飽和度, 亮度分別計算, 所以能夠單純保留住顏色然後對飽和度亮度作處理.
所以就把顏色保留住, 然後建立一組飽和度與亮度均為 255 的資料.
最後將原來的顏色, 新的飽和度與亮度結合成為一組新的 IHS 資料, 最後再將 IHS 資料轉回 RGB 的圖.
這種處理方式有個應用在人臉辨識系統, 你可以指定人臉的顏色像素範圍在 IHS 值中 H 的範圍, 轉為 IHS 之後將範圍內與範圍外的像素分別做不同的處理, 讓範圍值內的像素突顯出來.
這樣就能夠把人臉部分的影像區分出來.

refernce

jaistuff
ColorConvertDescriptor
ColorModel
SampleModel
IHSColorSpace
ImageLayout
JAI.KEY_IMAGE_LAYOUT
RenderingHints
HSV色彩屬性模式

codes

這段程式中的 ImageLayout 用來存整合顏色資料時候 RenderedHints 需要的資料如 ColorModel 與 SampleModel.
SampleModel 的作用是拿來存放每個像素的資料. ColorModel 是把像素資料轉成顏色, ColorSpace 是提供 ColorModel 如何轉換的資訊.
colorconvert 的時候需要的是要轉換的圖與要使用的 ColorModel, 而 bandmerge 則需要 merge 的資料與包含 ColorModel, SampleModel 資訊的 RenderedHints.
RenderedHints 透過 JAI.KEY_IMAGE_LAYOUT 來表示這個 hint 要處理的是 ImageLayout, 並在 RenderedHints 初始的時候提供一個 ImageLayout.
雖然看起來大概知道這樣的程式在處理什麼事情, 但和 PhotoShop 比較之後卻有不同的效果.
PhotoShop CS2 開啟同一張圖片後選擇 Image -> Adjustments -> Hue/Saturation 然後調整 Saturation 與 Lightness.
感覺起來 PhotoShop 比較準確, JAI 處理的結果很難用 PhotoShop 重製效果. 不知道是 colorconvert 使用的演算法不一樣還是相關的參數調整不同.
想想應該是調整個參數還要更仔細吧..
public class JAITester {

    private RenderedImage doOrderedDither(RenderedImage srcImage) {
        //prepare ihs color model can convert original image pixels' colors
        ColorSpace ihsColorSpace = IHSColorSpace.getInstance();
        ColorModel ihsColorModel = 
                new ComponentColorModel(ihsColorSpace, 
                new int[]{8, 8, 8}, 
                false, false, 
                Transparency.OPAQUE, 
                DataBuffer.TYPE_BYTE);
        ParameterBlock ihsParams = new ParameterBlock();
        ihsParams.addSource(srcImage);
        ihsParams.add(ihsColorModel);
        RenderedImage ihsImage = JAI.create("colorconvert", ihsParams);
        
        // select Hue data from color converted image data
        ParameterBlock bandSelectParams = new ParameterBlock();
        bandSelectParams.addSource( ihsImage );
        bandSelectParams.add( new int[]{1} );
        RenderedImage bandHueImage = JAI.create("bandselect", bandSelectParams);
        
        // prepare Intensity and Saturation data 
        ParameterBlock constParams = new ParameterBlock();
        constParams.add( (float) srcImage.getWidth() );
        constParams.add( (float) srcImage.getHeight() );
        constParams.add( new Byte[]{ (byte) 255 } );
        RenderedImage newI = JAI.create("constant", constParams);
        RenderedImage newS = JAI.create("constant", constParams);
        
        // prepare rendered hints to merge data with Hue data
        ImageLayout imageLayout = new ImageLayout();
        imageLayout.setColorModel(ihsColorModel);
        imageLayout.setSampleModel(ihsImage.getSampleModel());
        RenderingHints rendHints = 
                new RenderingHints(JAI.KEY_IMAGE_LAYOUT, imageLayout);
        
        // merge data with rendered hints
        ParameterBlock modifiedParams = new ParameterBlock();
        modifiedParams.addSource( newI );
        modifiedParams.addSource( bandHueImage );
        modifiedParams.addSource( newS );
        RenderedImage modifiedImage = 
                JAI.create("bandmerge", modifiedParams, rendHints);
        
        // convert ihs data to rgb data
        ParameterBlock finalParams = new ParameterBlock();
        finalParams.addSource( modifiedImage );
        finalParams.add( srcImage.getColorModel() );
        RenderedImage finalImage = JAI.create("colorconvert", finalParams);
        
        return finalImage;
    }

    /** 
     * http://forums.java.net/jive/message.jspa?messageID=221209
     * this gets rid of exception for not using native acceleration */
    static {
        System.setProperty("com.sun.media.jai.disableMediaLib", "true");
    }
    private static final String IMG_FILE_1 = "test2.jpg";

    public static void main(String[] args) {
        JAITester test = new JAITester();
        test.test();
    }

    private void test() {
        PlanarImage input = JAI.create("fileload", IMG_FILE_1);
        JPanel jPanelMain = new JPanel(new MigLayout("", "grow", "grow"));
        jPanelMain.add(new JScrollPane(new DisplayJAI(input)), "grow");
        jPanelMain.add(new JScrollPane(new DisplayJAI(doOrderedDither(input))), "grow");
        show(jPanelMain);
    }

    private void show(JComponent comp) {
        JFrame f = new JFrame();
        f.setLocation(50, 50);
        f.setPreferredSize(new Dimension(800, 600));
        f.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        f.getContentPane().add(comp);
        f.pack();
        f.setVisible(true);
    }
}    

JAI - 轉換像素資料 (ColorConvertDescriptor)

description

colorconverter 用來轉換像素顏色, 只要放指定的 ColorModel 進去就可以幫你轉換.
ColorModel 的作用是轉換像素的資料成為顏色, 而轉換成顏色的方式則透過 ColorSpace.

reference

jaistuff
ColorConvertDescriptor
IHSColorSpace

codes

使用 colorconvert 主要目的是轉換項宿資料, 而資料放在 RenderedImage 裡面.
這會有一個現象就是原本呈現顏色的時候是使用 RGB, 但因為轉換成 IHS 的 ColorSpace, 所以和 RGB 不一樣了.
但呈現的時候又以 RGB 的方式去解讀 IHS 導致顏色和原來的不同.
使用上 colorconvert 只是一個中繼點, 轉成 IHSColorSpace 之後拿 RenderedImage 的像素資料作處理, 最後再轉回 RGB.
這裡的 IHSColorSpace 是麻煩的東西, 因為網路上找到的都是 HSV/HSL/HSB, 也就是 H:顏色, S:飽和度, V/B:亮度.
那 api 上說的 ISH 則是 I:Intensity, H:Hue, S:Saturation. 只能猜他們的意思是一樣的.
除此之外從 IHSColorSpace 的 class name 是否能認定他們從 RGB 轉換過去的 float[0] 為 I, float[1] 為 H, float[2] 為 S 呢?
現在只能猜一猜, 如果以後有發現答案再說了.
public class JAITester {

    private RenderedImage doOrderedDither(RenderedImage image) {
        ColorSpace ihsColorSpace = IHSColorSpace.getInstance();
        ColorModel ihsColorModel = new ComponentColorModel(ihsColorSpace, new int[]{8, 8, 8}, false, false, Transparency.OPAQUE, DataBuffer.TYPE_BYTE);
        ParameterBlock pb = new ParameterBlock();
        pb.addSource(image);
        pb.add(ihsColorModel);
        RenderedImage ihsImage = JAI.create("colorconvert", pb);
        return ihsImage;
    }

    /** 
     * http://forums.java.net/jive/message.jspa?messageID=221209
     * this gets rid of exception for not using native acceleration */
    static {
        System.setProperty("com.sun.media.jai.disableMediaLib", "true");
    }
    private static final String IMG_FILE_1 = "test2.jpg";

    public static void main(String[] args) {
        JAITester test = new JAITester();
        test.test();
    }

    private void test() {
        PlanarImage input = JAI.create("fileload", IMG_FILE_1);
        JPanel jPanelMain = new JPanel(new MigLayout("", "grow", "grow"));
        jPanelMain.add(new JScrollPane(new DisplayJAI(input)), "grow");
        jPanelMain.add(new JScrollPane(new DisplayJAI(doOrderedDither(input))), "grow");
        show(jPanelMain);
    }

    private void show(JComponent comp) {
        JFrame f = new JFrame();
        f.setLocation(50, 50);
        f.setPreferredSize(new Dimension(800, 600));
        f.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        f.getContentPane().add(comp);
        f.pack();
        f.setVisible(true);
    }
}    

JAI - 指定像素資料 (ConstantDescriptor)

description

constant 可以用來建立指定寬高與顏色的 RenderedImage.
這種東西看到的用法是用來建立一個方便與其他影像顏色的結合, 比方說要調整亮度的時候就建立一個指定亮度的 RenderedImage, 然後把原來的圖和 Constant 建立的 RenderedImage 結合就成為指定亮度的圖.

reference

jaistuff
ConstantDescriptor

codes

public class JAITester {

    private RenderedImage doOrderedDither(RenderedImage image) {
        ParameterBlock constParams = new ParameterBlock();
        constParams.add( (float) image.getWidth() );
        constParams.add( (float) image.getHeight() );
        constParams.add( new Byte[]{ (byte)20, (byte)200, (byte) 200, (byte) 30} );
        image = JAI.create("constant", constParams);
        return image;
    }

    /** 
     * http://forums.java.net/jive/message.jspa?messageID=221209
     * this gets rid of exception for not using native acceleration */
    static {
        System.setProperty("com.sun.media.jai.disableMediaLib", "true");
    }
    private static final String IMG_FILE_1 = "test2.jpg";

    public static void main(String[] args) {
        JAITester test = new JAITester();
        test.test();
    }

    private void test() {
        PlanarImage input = JAI.create("fileload", IMG_FILE_1);
        JPanel jPanelMain = new JPanel(new MigLayout("", "grow", "grow"));
        jPanelMain.add(new JScrollPane(new DisplayJAI(input)), "grow");
        jPanelMain.add(new JScrollPane(new DisplayJAI(doOrderedDither(input))), "grow");
        show(jPanelMain);
    }

    private void show(JComponent comp) {
        JFrame f = new JFrame();
        f.setLocation(50, 50);
        f.setPreferredSize(new Dimension(800, 600));
        f.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        f.getContentPane().add(comp);
        f.pack();
        f.setVisible(true);
    }
}    

星期一 九月 01, 2008

JAI - 有序抖色法 Ordered dither 減少影像資料, 但視覺上差不多

description

有序抖色法 (Ordered dither) 給定一組陣列, 透過比較像素資訊與陣列後, 換成给定的一組顏色中最接近的顏色, 讓需要的顏色變少了, 看起來卻差不多 (因為顏色分成多階以及人類視覺習慣的關係).

reference

劉岱佑 劉敏 數位影像處理技術手冊 P7137 文魁資訊 2007 年 9 月
OrderedDitherDescriptor

codes

使用的 ColorCube 與 KernelJAI 都可以自訂, 不過用 JAI 提供的好像能達到適中的效果.
public class JAITester {

    private RenderedImage doOrderedDither(RenderedImage image) {
        pixelToFile( new File("build/before.txt"), image );
        ParameterBlock odParams = new ParameterBlock();
        odParams.addSource(image);
        odParams.add( ColorCube.BYTE_496 );
        odParams.add( KernelJAI.DITHER_MASK_443 );
        return pixelToFile( new File("build/after.txt"), JAI.create("OrderedDither", odParams) );
    }

    private RenderedImage pixelToFile(File outputFile, RenderedImage img) {
        StringBuffer sb = new StringBuffer();
        int w = img.getWidth();
        int h = img.getHeight();
        for (int x = 0; x < w; x++) {
            for (int y = 0; y < h; y++) {
                sb.append("[" + x + ", " + y + "]:[");
                double[] pixel = img.getData().getPixel(x, y, (double[]) null);
                for (int i = 0; i < pixel.length; i++) {

                    if (i == pixel.length - 1) {
                        sb.append(pixel[i]);
                    } else {
                        sb.append(pixel[i] + ", ");
                    }
                }
                sb.append("]");
            }
            sb.append("\n");
        }
        try {
            FileWriter f = new FileWriter(outputFile);
            f.write(sb.substring(0));
            f.flush();
            f.close();
        } catch (FileNotFoundException ex) {
            Logger.getLogger(JAITester.class.getName()).log(Level.SEVERE, null, ex);
        } catch (IOException ex) {
            Logger.getLogger(JAITester.class.getName()).log(Level.SEVERE, null, ex);
        }
        return img;
    }

    /** 
     * http://forums.java.net/jive/message.jspa?messageID=221209
     * this gets rid of exception for not using native acceleration */
    static {
        System.setProperty("com.sun.media.jai.disableMediaLib", "true");
    }
    private static final String IMG_FILE_1 = "test2.jpg";

    public static void main(String[] args) {
        JAITester test = new JAITester();
        test.test();
    }

    private void test() {
        PlanarImage input = JAI.create("fileload", IMG_FILE_1);
        JPanel jPanelMain = new JPanel(new MigLayout("", "grow", "grow"));
        jPanelMain.add(new JScrollPane(new DisplayJAI(input)), "grow");
        jPanelMain.add(new JScrollPane(new DisplayJAI(doOrderedDither(input))), "grow");
        show(jPanelMain);
    }

    private void show(JComponent comp) {
        JFrame f = new JFrame();
        f.setLocation(50, 50);
        f.setPreferredSize(new Dimension(800, 600));
        f.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        f.getContentPane().add(comp);
        f.pack();
        f.setVisible(true);
    }
}    

星期六 八月 30, 2008

JAI - open / close 效果. 清除雜點

description

erode 是腐蝕效果, 大概的算法是比較每個像素與指定的陣列, 取最小值, 所以值會愈來愈小, 灰階的小值顏色偏黑, 所以 erode 效果可以刪掉白點.
dilate 是膨脹效果, 和 erode 不同的是 erode 值會愈來愈小, dilate 則是愈來愈大, 所以叫膨脹. 灰階圖的值愈大就偏白色, 所以就可拿來清除黑點.
open 效果作法是先 erode 再 dilate, 就是先清掉白點再清黑點, 這樣結果圖黑色範圍會變大.
close 效果則是反過來, 先 dilate 再 erode, 先清黑點再清白點, 結果白點的範圍會變大.

reference

jaistuff

codes

想試 open 效果或 close 效果, 只要改 selected 變數值就可以了.
public class JAITester {

    float[] kernelMatrix  = new float[]{
        0,0,0,0,0,
        0,1,1,1,0,
        0,1,1,1,0,
        0,1,1,1,0,
        0,0,0,0,0
    };

    private enum OPEN_CLOSE { OPEN, CLOSE }
    private OPEN_CLOSE selected = OPEN_CLOSE.CLOSE;
    
    private RenderedImage doOpenOrClose(RenderedImage image) {
        image = toGrayScale(image);
        KernelJAI kernel = new KernelJAI( 5, 5, kernelMatrix );
        
        if ( selected == OPEN_CLOSE.OPEN ) {
            image = doErode(image, kernel);
            image = doDeliate(image, kernel);
        } else if ( selected == OPEN_CLOSE.CLOSE ) {
            image = doDeliate(image, kernel);            
            image = doErode(image, kernel);
        }
        
        return image;
    }

    private RenderedImage doDeliate(RenderedImage image, KernelJAI kernel) {
        ParameterBlock dilateParams = new ParameterBlock();
        dilateParams.addSource( image );
        dilateParams.add( kernel );
        return JAI.create("dilate", dilateParams, null);
    }
    
    private RenderedImage doErode(RenderedImage image, KernelJAI kernel) {
        ParameterBlock erodeParams = new ParameterBlock();
        erodeParams.addSource( image );
        erodeParams.add( kernel );
        return JAI.create("erode", erodeParams, null);
    }
    
    private RenderedImage toGrayScale(RenderedImage imageToBinarize) {
        ParameterBlock bandCombineParams = new ParameterBlock();
        bandCombineParams.addSource(imageToBinarize);
        bandCombineParams.add( new double[][]{ {0.2,0.3,0.4,0.5} } );
        return JAI.create("bandcombine", bandCombineParams, null);
    }

    /** 
     * http://forums.java.net/jive/message.jspa?messageID=221209
     * this gets rid of exception for not using native acceleration */
    static {
        System.setProperty("com.sun.media.jai.disableMediaLib", "true");
    }
    private static final String IMG_FILE_1 = "test7.jpg";

    public static void main(String[] args) {
        JAITester test = new JAITester();
        test.test();
    }

    private void test() {
        PlanarImage input = JAI.create("fileload", IMG_FILE_1);
        JPanel jPanelMain = new JPanel(new MigLayout("", "grow", "grow"));
        jPanelMain.add(new JScrollPane(new DisplayJAI(input)), "grow");
        jPanelMain.add(new JScrollPane(new DisplayJAI(doOpenOrClose(input))), "grow");
        show(jPanelMain);
    }

    private void show(JComponent comp) {
        JFrame f = new JFrame();
        f.setLocation(50, 50);
        f.setPreferredSize(new Dimension(800, 600));
        f.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        f.getContentPane().add(comp);
        f.pack();
        f.setVisible(true);
    }
}    

JAI - bandMerge 把兩張圖的顏色放在一張圖

description

使用 BandMerge 可以合併不同影像的 band 資訊.
比方說第一章圖的 (0, 0) 顏色為 (91.0), 第二張圖的 (0, 0) 顏色為 (158.0), 則透過 bandMerge 可以產生一張圖 (0,0) 會是 (91.0,158.0).
這裡要注意, 如果第一章圖 (0, 0) 是 (1,2,3), 第二張圖 (0,0) 是 (4,5,6), 則 bandMerge 後 (0,0) 變成 (1,2,3,4,5,6), 就會錯, 應該沒這種圖吧? 至少 swing 會有 exception.

reference

jaistuff

codes

注意在 setSource 的時候如果有兩個 0 的圖, 第一個會被第二個圖置換掉(好像很明顯, 不過我還是曾想說會不會混在一起, 測試結果是會被換掉).
public class JAITester {

    private PlanarImage doBandMerge() {
        ParameterBlockJAI mergeParams = new ParameterBlockJAI("bandmerge");
        mergeParams.setSource( pixelToFile(new File("build/test1.txt"), toGrayScale( JAI.create("fileload", "test1.jpg") ) ), 0);
        mergeParams.setSource( pixelToFile(new File("build/test2.txt"), toGrayScale( JAI.create("fileload", "test2.jpg") ) ), 1);
        mergeParams.setSource( pixelToFile(new File("build/test3.txt"), toGrayScale( JAI.create("fileload", "test3.jpg") ) ), 2);
        PlanarImage result = JAI.create( "bandmerge", mergeParams, null);
        pixelToFile( new File("build/bandmergeresult.txt"), result );
        return result;
    }

    private PlanarImage toGrayScale(PlanarImage imageToBinarize) {
        ParameterBlock bandCombineParams = new ParameterBlock();
        bandCombineParams.addSource(imageToBinarize);
        bandCombineParams.add( new double[][]{ {0.2,0.3,0.4,0.5} } );
        return JAI.create("bandcombine", bandCombineParams, null);
    }
    
    private RenderedImage pixelToFile(File outputFile, RenderedImage img) {
        StringBuffer sb = new StringBuffer();
        int w = img.getWidth();
        int h = img.getHeight();
        for (int x = 0; x < w; x++) {
            for (int y = 0; y < h; y++) {
                sb.append("[" + x + ", " + y + "]:[");
                double[] pixel = img.getData().getPixel(x, y, (double[]) null);
                for (int i = 0; i < pixel.length; i++) {

                    if (i == pixel.length - 1) {
                        sb.append(pixel[i]);
                    } else {
                        sb.append(pixel[i] + ", ");
                    }
                }
                sb.append("]");
            }
            sb.append("\n");
        }
        try {
            FileWriter f = new FileWriter(outputFile);
            f.write(sb.substring(0));
            f.flush();
            f.close();
        } catch (FileNotFoundException ex) {
            Logger.getLogger(JAITester.class.getName()).log(Level.SEVERE, null, ex);
        } catch (IOException ex) {
            Logger.getLogger(JAITester.class.getName()).log(Level.SEVERE, null, ex);
        }
        return img;
    }
    
    /** 
     * http://forums.java.net/jive/message.jspa?messageID=221209
     * this gets rid of exception for not using native acceleration */
    static {
        System.setProperty("com.sun.media.jai.disableMediaLib", "true");
    }
    private static final String IMG_FILE_1 = "test2.jpg";

    public static void main(String[] args) {
        JAITester test = new JAITester();
        test.test();
    }

    private void test() {
        PlanarImage input = JAI.create("fileload", IMG_FILE_1);
        JPanel jPanelMain = new JPanel(new MigLayout("", "grow", "grow"));
        jPanelMain.add(new JScrollPane(new DisplayJAI(input)), "grow");
        jPanelMain.add(new JScrollPane(new DisplayJAI(doBandMerge())), "grow");
        show(jPanelMain);
    }

    private void show(JComponent comp) {
        JFrame f = new JFrame();
        f.setLocation(50, 50);
        f.setPreferredSize(new Dimension(800, 600));
        f.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        f.getContentPane().add(comp);
        f.pack();
        f.setVisible(true);
    }
}    

JAI - bandSelect 指定需要的band資料

description

BandSelect 用來指定來源圖 band 的資訊, 複製到目的圖上.
比方說來源圖(0, 0)顏色是 [254.0, 115.0, 12.0], 那可以指定目的圖要變成 [12.0,12.0,12.0,12.0]
就用 bandSelect 指定目地圖的四個 band 的資料都從來源圖的第四個 band 複製.

reference

jaistuff

codes

程式中 bandSelect 陣列也可任意改為 new int[]{1} 或 new int[]{0,1,2} 等都可以, 不過超出 band 範圍就會 java.lang.IllegalArgumentException: BandSelect operation requires band indices to be less than the number of bands of the source image. 或 NullPointerException.
public class JAITester {

    /** Present selected band */
    private int[] bandSelect = new int[]{2, 2, 2, 2};

    private PlanarImage doBandSelect(PlanarImage imageToBandSelect) {
        pixelToFile( new File("build/before.txt"), imageToBandSelect  );
        imageToBandSelect = JAI.create("bandselect", imageToBandSelect, bandSelect);
        pixelToFile( new File("build/after.txt"), imageToBandSelect  );
        return imageToBandSelect;
    }

    private void pixelToFile(File outputFile, RenderedImage img) {
        StringBuffer sb = new StringBuffer();
        int w = img.getWidth();
        int h = img.getHeight();
        for (int x = 0; x < w; x++) {
            for (int y = 0; y < h; y++) {
                sb.append("[" + x + ", " + y + "]:[");
                double[] pixel = img.getData().getPixel(x, y, (double[]) null);
                for (int i = 0; i < pixel.length; i++) {

                    if (i == pixel.length - 1) {
                        sb.append(pixel[i]);
                    } else {
                        sb.append(pixel[i] + ", ");
                    }
                }
                sb.append("]");
            }
            sb.append("\n");
        }
        try {
            FileWriter f = new FileWriter(outputFile);
            f.write(sb.substring(0));
            f.flush();
            f.close();
        } catch (FileNotFoundException ex) {
            Logger.getLogger(JAITester.class.getName()).log(Level.SEVERE, null, ex);
        } catch (IOException ex) {
            Logger.getLogger(JAITester.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
    /** 
     * http://forums.java.net/jive/message.jspa?messageID=221209
     * this gets rid of exception for not using native acceleration */
    

    static {
        System.setProperty("com.sun.media.jai.disableMediaLib", "true");
    }
    private static final String IMG_FILE_1 = "test2.jpg";

    public static void main(String[] args) {
        JAITester test = new JAITester();
        test.test();
    }

    private void test() {
        PlanarImage input = JAI.create("fileload", IMG_FILE_1);
        JPanel jPanelMain = new JPanel(new MigLayout("", "grow", "grow"));
        jPanelMain.add(new JScrollPane(new DisplayJAI(input)), "grow");
        jPanelMain.add(new JScrollPane(new DisplayJAI(doBandSelect(input))), "grow");
        show(jPanelMain);
    }

    private void show(JComponent comp) {
        JFrame f = new JFrame();
        f.setLocation(50, 50);
        f.setPreferredSize(new Dimension(800, 600));
        f.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        f.getContentPane().add(comp);
        f.pack();
        f.setVisible(true);
    }
}    

JAI - binarize 二值化

description

binarize 中文叫做二值化, 查了很久才找到, 似乎是最基礎的東西...
二值化主要就是處理灰階的圖 (0 ~ 255), 定一個門檻, 高於門檻就設成 255, 低於門檻就設成 0. 所以才叫二值化.

reference

jaistuff
二值化

codes

第一步先把彩色影像轉成 1 個 band 的圖, one-band 是規定的, 不是的話就會 java.lang.IllegalArgumentException: Binarize source image must be single-banded., 我猜這一個 band 就是灰階的圖.
轉成灰階之後就可以拿 threshold 這個門檻放進 ParameterBlock 做判斷, 最後透過 JAI.create 產生判斷後的結果圖.
public class JAITester {

    private double threshold = 128;
    
    private PlanarImage doBinarize(PlanarImage imageToBinarize) {
        // convert to grayscale
        ParameterBlock bandCombineParams = new ParameterBlock();
        bandCombineParams.addSource(imageToBinarize);
        bandCombineParams.add( new double[][]{ {0.2,0.3,0.4,0.5} } );
        imageToBinarize = JAI.create("bandcombine", bandCombineParams, null);

        // binarize
        ParameterBlock binarizeParams = new ParameterBlock();
        binarizeParams.addSource( imageToBinarize  );
        binarizeParams.add( threshold );
        
        return JAI.create("binarize", binarizeParams);
    }

    /** 
     * http://forums.java.net/jive/message.jspa?messageID=221209
     * this gets rid of exception for not using native acceleration */
    static {
        System.setProperty("com.sun.media.jai.disableMediaLib", "true");
    }
    private static final String IMG_FILE_1 = "test2.jpg";

    public static void main(String[] args) {
        JAITester test = new JAITester();
        test.test();
    }

    private void test() {
        PlanarImage input = JAI.create("fileload", IMG_FILE_1);
        JPanel jPanelMain = new JPanel(new MigLayout("", "grow", "grow"));
        jPanelMain.add(new JScrollPane(new DisplayJAI(input)), "grow");
        jPanelMain.add(new JScrollPane(new DisplayJAI(doBinarize(input))), "grow");
        show(jPanelMain);
    }

    private void show(JComponent comp) {
        JFrame f = new JFrame();
        f.setLocation(50, 50);
        f.setPreferredSize(new Dimension(800, 600));
        f.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        f.getContentPane().add(comp);
        f.pack();
        f.setVisible(true);
    }
}    

星期三 八月 27, 2008

JAI - 色階分佈 Histogram

description

Histogram 就是直方圖, 就是色階分布圖, 也就是一張圖每個顏色有多少像素的統計圖.
比方說一張圖顏色若是 0 ~ 255, 則 Histogram 上就會表現出 0 這個顏色有幾個像素, 1 有幾個 ... 255 有幾個.
Histogram 就是這樣的統計表. 這種統計表用 Photoshop 可以看到.

reference

jaistuff

codes

參考 HistogramDescriptor api doc 上所寫的, 想取得 Histogram 可先設定以下參數, 以 JAI.create 建立影像, 最後透過 getProperty( "histogram" ) 取得 Histogram 的物件.
這些參數大概的意思: ROI, xPeriod, yPeriod 用來指定區間; numBins 指定這個 Histogram 要用幾個 bin 來呈現. lowValue 與 highValue 則是指定要檢查的最高與最低像素值.
前面有說 Histogram 的意思就是用來統計每個顏色有幾個像素. 而 bin 就代表一個顏色. 然後就像 band 一樣, 一張圖不一定只有一組 bin.
這裡其實對於各參數以及 band, bin 等的意義都還很模糊. 就先看 API 怎麼使用, 以後再慢慢搞懂這些名詞的意義為何. 如果有人學過請不吝賜教, 缸溫.

Parameter List
Name Class Type Default Value
roi javax.media.jai.ROI null
xPeriod java.lang.Integer 1
yPeriod java.lang.Integer 1
numBins int[] {256}
lowValue double[] {0.0}
highValue double[] {256.0}

以下程式只把 Histogram 的 bins 值印出, 裡面還有很多資料沒呈現.
public class JAITester {

    private PlanarImage doHistogram(PlanarImage imageToHistogram) {
        ParameterBlock pb = new ParameterBlock();
        pb.addSource( imageToHistogram );
        pb.add( null ); // region-of-interest (ROI)
        pb.add( 1 ); // xPeriod
        pb.add( 1 ); // yPeriod
        
        // numBins : must have an array length of 1
        pb.add( new int[]{256} ); 
        
        // lowValue : must have an array length of 1
        pb.add( new double[]{0} ); 
        
        // highValue : must have an array length of 1
        pb.add( new double[]{256} ); 
        imageToHistogram = JAI.create( "histogram", pb );
        Histogram h = (Histogram) imageToHistogram.getProperty("histogram");
        int[][] bins = h.getBins();
        for ( int i = 0; i < bins.length; i++ ) {
            for ( int j = 0; j < bins[i].length; j++ ) {
                System.out.println("(" + i + "," + j + ")=" + bins[i][j]);
            }
        }
        return imageToHistogram;
    }

    /** 
     * http://forums.java.net/jive/message.jspa?messageID=221209
     * this gets rid of exception for not using native acceleration */
    static {
        System.setProperty("com.sun.media.jai.disableMediaLib", "true");
    }
    private static final String IMG_FILE_1 = "test2.jpg";

    public static void main(String[] args) {
        JAITester test = new JAITester();
        test.test();
    }

    private void test() {
        PlanarImage input = JAI.create("fileload", IMG_FILE_1);
        JPanel jPanelMain = new JPanel(new MigLayout("", "grow", "grow"));
        jPanelMain.add(new JScrollPane(new DisplayJAI(input)), "grow");
        jPanelMain.add(new JScrollPane(new DisplayJAI(doHistogram(input))), "grow");
        show(jPanelMain);
    }

    private void show(JComponent comp) {
        JFrame f = new JFrame();
        f.setLocation(50, 50);
        f.setPreferredSize(new Dimension(800, 600));
        f.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        f.getContentPane().add(comp);
        f.pack();
        f.setVisible(true);
    }
}    

星期日 八月 24, 2008

JAI - BandCombine 處理

description

在看 Histogram 的時候發現有個東西搞不懂就是 BandCombine.
沒上過圖學的課, 所以不懂 Band 要怎麼解釋.
在看過 api doc 之後再觀察一下總算有點概念, 但也都是猜的.
希望有學過的人能分享一下, 感謝.

reference

jaistuff
BandCombineDescriptor

Example

使用 bandCombine 首先要指定一個陣列. 這個陣列 size 是規定好的.
double[][] matrix = new double[destBands][sourceBands + 1];
sourceBands 是來源圖的 band, destBands 目地圖的 band. 這個 band 可以透過 RenderedImage 呼叫 RenderedImage.getSampleModel().getNumBands(); 取得.
我測 jpg, png 都是 3.
指定好後, BandCombine 會透過 api 上寫的公式計算
// s = source pixel
// d = destination pixel
for(int i = 0; i < destBands; i++) {
    d[i] = matrix[i][sourceBands];
    for(int j = 0; j < sourceBands; j++) {
        d[i] += matrix[i][j]*s[j];
    }
}
這個公式其實蠻單純的,
假如你指定 matrix 是
{
    {0.5,0.6,0.7,0.1}
}     
這是候原圖的像素 [0,0] 的值為 [101.0, 81.0, 48.0]
這樣套在公式上,
s[] = {101, 81, 48} 
destBands = 1
sourceBands = 3
計算過程就是
matrix = {0.5,0.6,0.7,0.1}
i = 0 => d[0] = matrix[0][3] = 0.1
  j = 0 => d[0] = d[0] + matrix[0][0] * s[0] =  0.1 + 0.5 * 101 =  50.6
  j = 1 => d[0] = d[0] + matrix[0][1] * s[1] = 50.6 + 0.6 *  81 =  99.2
  j = 2 => d[0] = d[0] + matrix[0][2] * s[2] = 99.2 + 0.7 *  48 = 132.8 (final)

所以 [0,0] 在 BandCombine 之後就變成 [132.8], 然後在讀取 SampleModel 的 Data 時發現好像有四捨五入就變 133.
如果陣列改為
{
    {0.5,0.6,0.7,0.1},
    {0.2,0.3,0.4,0.8}
};
上面已經算出第一個陣列值的結果是 132.8, 接下來再算第二個陣列.
matrix = {0.2,0.3,0.4,0.8}
i = 0 => d[0] = matrix[0][3] = 0.1
  j = 0 => d[0] = d[0] + matrix[0][0] * s[0] =  0.8 + 0.2 * 101 = 21.0
  j = 1 => d[0] = d[0] + matrix[0][1] * s[1] = 21.0 + 0.3 *  81 = 45.3
  j = 2 => d[0] = d[0] + matrix[0][2] * s[2] = 45.6 + 0.4 *  48 = 64.8    
最後第二組陣列計算結果會讓像素 [0,0] 的值變成 [132.8, 64.8], 然後 RenderedImage.getSampleModel().getData() 的結果可看到像素 [0,0] = [133,65]
在這裡就不討論 [132.8] 和 [132.8, 64.8] 以及如果有更多的數字在這個陣列裡面代表什麼意思, 那是其他的 issue 了, 現在也不會, 只能猜這應該是 Red, Green, Blue 的值, 如果是(132.8, 64.8) 可能是 Red=132.8, Green=64.8 吧?
附上測試程式
public class JAITester {

    private PlanarImage doHistogram(PlanarImage imageToHistogram) {
        int numBands = imageToHistogram.getSampleModel().getNumBands();
        double[][] matrix = new double[][]{
            {0.5,0.6,0.7,0.1},
            {0.2,0.3,0.4,0.8}
        };
        pixelToFile(new File("build/beforeBandCombine.txt"), imageToHistogram);
        ParameterBlock pb = new ParameterBlock();
        pb.addSource(imageToHistogram);
        pb.add(matrix);
        imageToHistogram = JAI.create("bandcombine", pb, null);
        pixelToFile(new File("build/afterBandCombine.txt"), imageToHistogram);
        return imageToHistogram;
    }

    private void pixelToFile(File outputFile, RenderedImage img) {
        StringBuffer sb = new StringBuffer();
        int w = img.getWidth();
        int h = img.getHeight();
        for ( int x = 0; x < w; x++ ) {
            for ( int y = 0; y < h; y++ ) {
                sb.append("[" + x + ", " + y + "]:[");
                double[] pixel = img.getData().getPixel(x, y, (double[])null);
                for ( int i = 0; i < pixel.length; i++ ) {
                    
                    if ( i == pixel.length - 1 ) {
                        sb.append( pixel[i] );
                    } else {
                        sb.append( pixel[i] + ", " );
                    }
                }
                sb.append("]");
            }
            sb.append("\n");
        }
        try {
            FileWriter f = new FileWriter(outputFile);
            f.write(sb.substring(0));
            f.flush();
            f.close();
        } catch (FileNotFoundException ex) {
            Logger.getLogger(JAITester.class.getName()).log(Level.SEVERE, null, ex);
        } catch (IOException ex) {
            Logger.getLogger(JAITester.class.getName()).log(Level.SEVERE, null, ex);
        }         
    }

    /** 
     * http://forums.java.net/jive/message.jspa?messageID=221209
     * this gets rid of exception for not using native acceleration */
    static {
        System.setProperty("com.sun.media.jai.disableMediaLib", "true");
    }
    
    private static final String IMG_FILE_1 = "test1.jpg";

    public static void main(String[] args) {
        JAITester test = new JAITester();
        test.test();
    }

    private void test() {
        PlanarImage input = JAI.create("fileload", IMG_FILE_1);
        JPanel jPanelMain = new JPanel(new MigLayout("", "grow", "grow"));
        jPanelMain.add(new JScrollPane(new DisplayJAI(input)), "grow");
        jPanelMain.add(new JScrollPane(new DisplayJAI(doHistogram(input))), "grow");
        show(jPanelMain);
    }

    private void show(JComponent comp) {
        JFrame f = new JFrame();
        f.setLocation(50, 50);
        f.setPreferredSize(new Dimension(800, 600));
        f.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        f.getContentPane().add(comp);
        f.pack();
        f.setVisible(true);
    }
}