blockwise DCT
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.
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
}
}
