blockwise DCT

by wrotenodoc forked from DCT (diff: 202)
- perform DCT for each 8x8 block
- luminance quantization applied (using standard Q table)
- you can zero from highest frequencies in 8 steps (use slider and buttons at bottom)

dct image is highly oversaturated because all values other than DC term is too low. red pixel means negative frequency value.
♥0 | Line 252 | Modified 2015-11-07 13:02:36 | MIT License
play

ActionScript3 source code

/**
 * Copyright wrotenodoc ( http://wonderfl.net/user/wrotenodoc )
 * MIT License ( http://www.opensource.org/licenses/mit-license.php )
 * Downloaded from: http://wonderfl.net/c/alnC
 */

// forked from wrotenodoc's DCT
// forked from wrotenodoc's DFT
package {

    import flash.display.Sprite
    import flash.display.Bitmap
    import flash.display.BitmapData
    import flash.text.TextField
    import flash.text.TextFormat
    import flash.geom.Point
    
    import com.bit101.components.*
    
    public class DCT_Test extends Sprite {
        
        private var originalBD:BitmapData
        
        private var dctBackup:DCTResult, dct:DCTResult, dctBD:BitmapData, dctBMP:Bitmap
        private var dctDebug:TextField, dctRect:Sprite
        
        private var idctBD:BitmapData, idctBMP:Bitmap
        
        private var cutter:HSlider, cutterLabel:Label
        private var noCut:RadioButton, verticalOnly:RadioButton, horizontalOnly:RadioButton, vhBoth:RadioButton
        
        public function DCT_Test() {
            // original image
            originalBD = new BitmapData(64, 64, false, 0x0)
            originalBD.perlinNoise(16, 16, 4, 0, false, true, 7, true, [new Point(30, 20)])
            var bmp:Bitmap = new Bitmap(originalBD)
            bmp.y = 15
            bmp.scaleX = bmp.scaleY = 3
            addChild(bmp)
            
            dctBackup = DCT(originalBD, 8, 8) // dct for each 8x8 block
            
            // dct coefficients (frequency spectrum)
            dctBD = originalBD.clone()
            dctBD.fillRect(dctBD.rect, 0x0)
            dctBMP = new Bitmap(dctBD)
            with(addChild(dctBMP)){
                x = bmp.x + bmp.width
                y = bmp.y
                scaleX = scaleY = bmp.scaleX
            }
            
            // idct result (restored image)
            idctBD = dctBD.clone()
            idctBMP = new Bitmap(idctBD)
            with(addChild(idctBMP)){
                scaleX = scaleY = bmp.scaleX
                x = bmp.x
                y = bmp.y + bmp.height
            }
            
            dctDebug = makeText("roll the mouse over dct image", dctBMP.x+dctBMP.width/2, dctBMP.y+dctBMP.height+10)
            dctDebug.autoSize = "left"
            dctDebug.defaultTextFormat = new TextFormat("consolas", 14)
            dctRect = new Sprite
            dctRect.graphics.lineStyle(0, 0xff0000)
            dctRect.graphics.drawRect(0, 0, 8, 8)
            dctRect.scaleX = dctBMP.scaleX
            dctRect.scaleY = dctBMP.scaleY
            addChild(dctRect)
            stage.addEventListener("mouseMove", stage_mouseMove)
            
            var tf1:TextField = makeText("original", bmp.x+bmp.width/2, bmp.y-10)
            var tf2:TextField = makeText("dct", dctBMP.x+dctBMP.width/2, dctBMP.y-10)
            var tf3:TextField = makeText("idct", idctBMP.x+idctBMP.width/2, idctBMP.y+idctBMP.height+10)
            function makeText(msg:String, tx:Number, ty:Number):TextField {
                var tf:TextField = new TextField
                tf.selectable = false
                tf.autoSize = "center"
                tf.text = msg
                tf.x = tx - tf.width/2 ; tf.y = ty - tf.height/2
                addChild(tf)
                return tf
            }
            
            cutter = new HSlider(this, idctBMP.x, idctBMP.y + idctBMP.height + 20, bandCutter)
            cutter.value = cutter.minimum = 0 ; cutter.maximum = 8 ; cutter.tick = 1
            cutter.width = dctBMP.width
            cutterLabel = new Label(this, cutter.x+cutter.width+10, cutter.y, cutter.value + "")
            noCut = new RadioButton(this, idctBMP.x, idctBMP.y + idctBMP.height + 35, "no band cutting", true, bandCutter)
            verticalOnly = new RadioButton(this, idctBMP.x + 100, idctBMP.y + idctBMP.height + 50, "vertical only", false, bandCutter)
            horizontalOnly = new RadioButton(this, idctBMP.x + 100, idctBMP.y + idctBMP.height + 35, "horizontal only", false, bandCutter)
            vhBoth = new RadioButton(this, idctBMP.x, idctBMP.y + idctBMP.height + 50, "both direction", false, bandCutter)
            
            render()
        }
        
        private function bandCutter(e:Object):void {
            cutterLabel.text = cutter.value + ""
            render()
        }
        
        private function cutBand(dct:DCTResult, width:uint):void {
            if(width == 0) return
            var H:Boolean = horizontalOnly.selected || vhBoth.selected
            var V:Boolean = verticalOnly.selected || vhBoth.selected
            var stepH:uint = dct.width / 8, stepV:uint = dct.height / 8
            if(H){
                for(var i:int=0; i<stepV; i++){
                    for(var j:int=0; j<stepH; j++){
                        cut(i*8+(8-width), j*8, width, 8)
                    }
                }
            }
            if(V){
                for(i=0; i<stepV; i++){
                    for(j=0; j<stepH; j++){
                        cut(i*8, j*8+(8-width), 8, width)
                    }
                }
            }
            function cut(y0:uint, x0:uint, h:uint, w:uint):void {
                for(var y:uint=y0; y<y0+h; y++){
                    for(var x:uint=x0; x<x0+w; x++){
                        dct.data[y][x] = 0
                    }
                }
            }
        }
        
        private function render():void {
            dct = dctBackup.clone()
            cutBand(dct, cutter.value)
            dct2bd(dct, dctBD)
            IDCT(dct, idctBD, 8, 8)
        }
        
        private function stage_mouseMove(e:Object):void {
            var bx:Boolean = dctBMP.x <= stage.mouseX && stage.mouseX < dctBMP.x + dctBMP.width*dctBMP.scaleX
            var by:Boolean = dctBMP.y <= stage.mouseY && stage.mouseY < dctBMP.y + dctBMP.height*dctBMP.scaleY
            if(bx && by){
                var x0:int = int((stage.mouseX - dctBMP.x)/dctBMP.scaleX/8)*8
                var y0:int = int((stage.mouseY - dctBMP.y)/dctBMP.scaleY/8)*8
                var txt:String = "quantized DCT\n"
                var i:int, j:int, maxLen:Array = []
                for(i=0; i<8; i++){
                    maxLen[i] = 1
                    for(j=0; j<8; j++){
                        maxLen[i] = Math.max(maxLen[i], String(int(dct.data[y0+j][x0+i])).length)
                    }
                }
                for(i=0; i<8; i++){
                    for(j=0; j<8; j++){
                        var val:String = String(int(dct.data[y0+i][x0+j]))
                        txt += val
                        var blank:int = maxLen[j] - val.length + 2
                        while(blank --> 0) txt += " "
                    }
                    txt += "\n"
                }
                dctDebug.text = txt
                dctRect.x = dctBMP.x + x0 * dctBMP.scaleX
                dctRect.y = dctBMP.y + y0 * dctBMP.scaleY
            }
        }
        
        private function dct2bd(dct:DCTResult, output:BitmapData):void {
            for(var y:int=0; y<dct.height; y++){
                for(var x:int=0; x<dct.width; x++){
                    var color:uint = uint(Math.sqrt(Math.abs(dct.data[y][x]))/(dct.maxValue - dct.minValue) * 255 * 255 * 20)
                    if(color > 255) color = 255
                    if(dct.data[y][x] >= 0) color = color<<16 | color<<8 | color
                    else color = color<<16
                    output.setPixel(x, y, color)
                }
            }
        }
        
        private const pi2:Number = Number.PI * 2
        private function DCT(image:BitmapData, blockWidth:uint, blockHeight:uint):DCTResult {
            var N:int = image.height
            var M:int = image.width
            var PI_M:Number = Math.PI / blockWidth
            var PI_N:Number = Math.PI / blockHeight
            var result:DCTResult = new DCTResult(M, N)
            var i:int, j:int
            
            result.minValue = Infinity
            result.maxValue = -Infinity
            var r:Number
            for(i=0; i<N; i++){
                for(j=0; j<M; j++){
                    result.data[i][j] = sum(i, j)
                    r = result.data[i][j]
                    if(r > result.maxValue) result.maxValue = r
                    if(r < result.minValue) result.minValue = r
                }
            }
            quantization(result)
            return result
            
            // (i, j) = pixel (y, x)
            function sum(i:int, j:int):Number {
                var real:Number = 0
                var col:uint
                var y0:uint = uint(i/blockHeight) * blockHeight
                var y1:uint = Math.min(y0 + blockHeight, N)
                var x0:uint = uint(j/blockWidth) * blockWidth
                var x1:uint = Math.min(x0 + blockWidth, M)
                for(var y:int=y0; y<y1; y++){
                    for(var x:int=x0; x<x1; x++){
                        col = image.getPixel(x, y) & 0xFF
                        real += 4 * col * Math.cos(PI_M*((x-x0)+.5)*(j-x0)) * Math.cos(PI_N*((y-y0)+.5)*(i-y0))
                    }
                }
                return real
            }
        }
        
        // (row-major) quantization matrix for 8x8 luminance block
        private const quantMatrix:Array = [
            [16, 11, 10, 16, 24, 40, 51, 61],
            [12, 12, 14, 19, 26, 58, 60, 55],
            [14, 13, 16, 24, 40, 57, 69, 56],
            [14, 17, 22, 29, 51, 87, 80, 62],
            [18, 22, 37, 56, 68, 109, 103, 77],
            [24, 35, 55, 64, 81, 104, 113, 92],
            [49, 64, 78, 87, 103, 121, 120, 101],
            [72, 92, 95, 98, 112, 100, 103, 99]]
        private function quantization(dct:DCTResult):void {
            // only works for 8x8 blockwise dct!
            for(var i:int=0; i<dct.height; i++){
                for(var j:int=0; j<dct.width; j++){
                    dct.data[i][j] /= quantMatrix[i%8][j%8]
                }
            }
        }
        
        private function IDCT(dct:DCTResult, output:BitmapData, blockWidth:uint, blockHeight:uint):void {
            var N:int = dct.height, M:int = dct.width
            var PI_M:Number = Math.PI / blockWidth
            var PI_N:Number = Math.PI / blockHeight
            var i:int, j:int
            
            var col:uint
            for(i=0; i<N; i++){
                for(j=0; j<M; j++){
                    col = uint(sum(i, j))
                    output.setPixel(j, i, col<<16 | col<<8 | col)
                }
            }
            
            function sum(i:int, j:int):Number {
                var real:Number = 0
                var y0:uint = uint(i/blockHeight) * blockHeight
                var y1:uint = Math.min(y0 + blockHeight, N)
                var x0:uint = uint(j/blockWidth) * blockWidth
                var x1:uint = Math.min(x0 + blockWidth, M)
                var blockSize:uint = (x1-x0) * (y1-y0)
                var w1:Number, w2:Number
                
                for(var y:int=y0; y<y1; y++){
                    w2 = y == y0 ? .5 : 1
                    for(var x:int=x0; x<x1; x++){
                        w1 = x == x0 ? .5 : 1
                        real += quantMatrix[y%8][x%8] * w1*w2*dct.data[y][x] * Math.cos(PI_M*((j-x0)+.5)*(x-x0)) * Math.cos(PI_N*((i-y0)+.5)*(y-y0))
                    }
                }
                return real / blockSize
            }
        }
        
    }
    
}

class DCTResult {
    public var data:Vector.<Vector.<Number>>
    public var maxValue:Number, minValue:Number
    public var width:int, height:int
    public function DCTResult(width:int, height:int) {
        data = new Vector.<Vector.<Number>>(height, true)
        for(var i:int=0; i<height; i++) data[i] = new Vector.<Number>(width, true)
        this.width = width
        this.height = height
    }
    public function clone():DCTResult {
        var dct:DCTResult = new DCTResult(width, height)
        dct.maxValue = maxValue
        dct.minValue = minValue
        dct.data = new Vector.<Vector.<Number>>
        for(var i:int=0; i<height; i++){
            dct.data[i] = data[i].concat()
        }
        return dct
    }
}