/// Flash implementation of Paul Bourke's 'YCC colour space and image compression' // paper at http://astronomy.swin.edu.au/~pbourke/colour/ycc/ // // Copyright 2005 tim maffett Creative Common Attribution 2.0 license, please notify me if you // use this // http://creativecommons.org/licenses/by/2.0/ // // 10-12-05 completed. Jon B pointed me to this on Mario Klingemann's blog entry // http://www.quasimondo.com/archives/000572.php#000572 // // As you can see Flash 8 can do this quite fast, and it is a good 'pre' compression for // other compression methods (RLE, BWT or LZW, etc.) (coming soon! ;) // // http://timmaffett.com timmaffett@gmail.com // var findMeString = "bourke experiment website: http://timmaffett.com " + "email: timmaffett@gmail.com"; // Do RGB Transform to YCbCr space // Y = 0.2989 R + 0.5866 G + 0.1145 B // Cb = -0.1687 R - 0.3312 G + 0.5000 B // Cr = 0.5000 R - 0.4183 G - 0.0816 B var toYCrCbMatrix = [ 0.2989, 0.5866, 0.1145, 0, 0, -0.1687, -0.3312, 0.5000, 0, 127.5, 0.5000, -0.4183, -0.0816, 0, 127.5, 0, 0, 0, 1, 0 ]; // alpha var toYCbCrFilter = new flash.filters.ColorMatrixFilter(toYCrCbMatrix); //Of course when the YCC values are turned back into RGB, then 127.5 must be first //subtracted from the two chrominance values. The reverse transform is //R = Y + 1.4022 Cr //G = Y - 0.3456 Cb - 0.7145 Cr //B = Y + 1.7710 Cb //rawBmp.draw(img, new flash.geom.Matrix(), flash.geom.ColorTransform() ); var yCrCbToRGBMatrix = [ 1, 0.0000, 1.4022, 0, (1.4022 * -127.5), 1, -0.3456, -0.7145, 0, (-0.3456 * -127.5) + (-0.7145 * -127.5), 1, 1.7710, 0.0000, 0, (1.7710* -127.5), 0, 0, 0, 1, 0]; // alpha var yCrCbToRGBFilter = new flash.filters.ColorMatrixFilter(yCrCbToRGBMatrix); // RGB -> CIE XYZitu (D65) // X = 0.431 R + 0.342 G + 0.178 B // Y = 0.222 R + 0.707 G + 0.071 B // Z = 0.020 R + 0.130 G + 0.939 B var toXYZituMatrix = [ 0.431, 0.342, 0.178, 0, 0, 0.222, 0.707, 0.071, 0, 0, 0.020, 0.130, 0.939, 0, 0, 0, 0, 0, 1, 0 ]; // alpha var toXYZituFilter = new flash.filters.ColorMatrixFilter(toXYZituMatrix); // CIE XYZitu (D65) -> RGB // R = 3.063 X - 1.393 Y - 0.476 Z // G = -0.969 X + 1.876 Y + 0.042 Z // B = 0.068 X - 0.229 Y + 1.069 Z var XYZituToRGBMatrix = [ 3.063, -1.393, -0.476, 0, 0, -0.969, 1.876, 0.042, 0, 0, 0.068, -0.229, 1.069, 0, 0, 0, 0, 0, 1, 0 ]; // alpha var XYZituToRGBFilter = new flash.filters.ColorMatrixFilter(XYZituToRGBMatrix); // RGB -> CIE XYZrec601-1 (C illuminant) // X = 0.607 R + 0.174 G + 0.200 B // Y = 0.299 R + 0.587 G + 0.114 B // Z = 0.000 R + 0.066 G + 1.116 B var toXYZrec601Matrix = [ 0.607, 0.174, 0.200, 0, 0, 0.299, 0.587, 0.114, 0, 0, 0.000, 0.066, 1.116, 0, 0, 0, 0, 0, 1, 0 ]; // alpha var toXYZrec601Filter = new flash.filters.ColorMatrixFilter(toXYZrec601Matrix); // CIE XYZrec601-1 (C illuminant) -> RGB // R = 1.910 X -0.532 Y -0.288 Z // G = -0.985 X +1.999 Y -0.028 Z // B = 0.058 X -0.118 Y +0.898 Z var XYZrec601ToRGBMatrix = [ 1.910, -0.532, -0.288, 0, 0, -0.985, 1.999, -0.028, 0, 0, 0.058, -0.118, 0.898, 0, 0, 0, 0, 0, 1, 0 ]; // alpha var XYZrec601ToRGBFilter = new flash.filters.ColorMatrixFilter(XYZrec601ToRGBMatrix); // RGB -> CIE XYZccir709 (D65) // X = 0.412 R + 0.358 G + 0.180 B // Y = 0.213 R + 0.715 G + 0.072 B // Z = 0.019 R + 0.119 G + 0.950 B var toXYZccir709Matrix = [ 0.412, 0.358, 0.180, 0, 0, 0.213, 0.715, 0.072, 0, 0, 0.019, 0.119, 0.950, 0, 0, 0, 0, 0, 1, 0 ]; // alpha var toXYZccir709Filter = new flash.filters.ColorMatrixFilter(toXYZccir709Matrix); // CIE XYZccir709 (D65) -> RGB // R = 3.241 X - 1.537 Y - 0.499 Z // G = -0.969 X + 1.876 Y + 0.042 Z // B = 0.056 X - 0.204 Y + 1.057 Z var XYZccir709ToRGBMatrix = [ 3.241, -1.537, -0.499, 0, 0, -0.969, 1.876, 0.042, 0, 0, 0.056, -0.204, 1.057, 0, 0, 0, 0, 0, 1, 0 ]; // alpha var XYZccir709ToRGBFilter = new flash.filters.ColorMatrixFilter(XYZccir709ToRGBMatrix); // PAL television standard // RGB -> YUV // Y = 0.299 R + 0.587 G + 0.114 B // U = -0.147 R - 0.289 G + 0.436 B // V = 0.615 R - 0.515 G - 0.100 B var toYUVMatrix = [ 0.299, 0.587, 0.114, 0, 0, -0.147, -0.289, 0.436, 0, 127.5, 0.615, -0.515, 0.100, 0, 127.5, 0, 0, 0, 1, 0 ]; // alpha var toYUVFilter = new flash.filters.ColorMatrixFilter(toYUVMatrix); // YUV -> RGB // R = Y + 0.000 U + 1.140 V // G = Y - 0.396 U - 0.581 V // B = Y + 2.029 U + 0.000 V var YUVToRGBMatrix = [ 1, 0.000, 1.140, 0, (1.140 * -127.5), 1,-0.396,-0.581, 0, (-0.396 * -127.5) + (-0.581 * -127.5), 1, 2.029, 0.000, 0, (2.029 * - 127.5), 0, 0, 0, 1, 0 ]; // alpha var YUVToRGBMatrixFilter = new flash.filters.ColorMatrixFilter(YUVToRGBMatrix); // NTSC television standard // RGB -> YIQ // Y = 0.299 R + 0.587 G + 0.114 B // I = 0.596 R - 0.274 G - 0.322 B // Q = 0.212 R - 0.523 G + 0.311 B var toYIQMatrix = [ 0.299, 0.587, 0.114, 0, 0, 0.596, -0.274, -0.322, 0, 127.5, 0.212, -0.523, 0.311, 0, 127.5, 0, 0, 0, 1, 0 ]; // alpha var toYIQFilter = new flash.filters.ColorMatrixFilter(toYIQMatrix); // YIQ -> RGB // R = Y + 0.956 I + 0.621 Q // G = Y - 0.272 I - 0.647 Q // B = Y - 1.105 I + 1.702 Q var YIQToRGBMatrix = [ 1, 0.956, 0.621, 0, (0.956 * -127.5) + (0.621 * -127.5), 1,-0.272,-0.647, 0, (-0.272 * -127.5) + (-0.647 * -127.5), 1,-1.105, 1.702, 0, (-1.105 * - 127.5) + (1.702 * -127.5), 0, 0, 0, 1, 0 ]; // alpha var YIQToRGBMatrixFilter = new flash.filters.ColorMatrixFilter(YIQToRGBMatrix); curFilterNum = 0; justYCbCr = false; //rgbToFilters[curFilterNum] //toRGBFilters[curFilterNum] var rgbToFilters = [ toYCbCrFilter,toXYZituFilter,toXYZrec601Filter, toXYZccir709Filter, toYUVFilter, toYIQFilter ]; var toRGBFilters = [ yCrCbToRGBFilter,XYZituToRGBFilter,XYZrec601ToRGBFilter, XYZccir709ToRGBFilter, YUVToRGBMatrixFilter, YIQToRGBMatrixFilter ]; var titleOfFilters = [ "RGB <-> YCrCb","RGB <-> XYZitu", "RGB <-> XYZrec601", "RGB <-> XYZccir709", "RGB <-> YUV (Pal tv)", "RGB <-> YIQ (NTSC tv)" ]; var titleOfChannelsFilters = [ ["Y", "Cr", "Cb", "YCrCb" ], // "RGB <-> yCrCb", ["X", "Y", "Z", "XYZitu (D56)" ], //"RGB <-> XYZitu", ["X", "Y", "Z", "XYZrec601-1 (C illuminant)" ], // "RGB <-> XYZrec601", ["X", "Y", "Z", "XYZccirc709 (D65)" ], //"RGB <-> XYZccir709", ["Y", "U", "V", "YUV (PAL tv standard)" ], // "RGB <-> YUV (Pal tv)", ["Y", "I", "Q", "YIQ (NTSC tv standard)" ] // "RGB <-> YIQ (NTSC tv)" ]; // make some filters // Now we copy R -> Blue color channel // red channel to ALL channels var allRedMatrix = [ 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0]; // alpha var theAllRedfilter = new flash.filters.ColorMatrixFilter(allRedMatrix); var allGreenMatrix = [ 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0]; // alpha var theAllGreenfilter = new flash.filters.ColorMatrixFilter(allGreenMatrix); var allBlueMatrix = [ 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0]; // alpha var theAllBluefilter = new flash.filters.ColorMatrixFilter(allBlueMatrix); function resetMapToBlockAndApply() { mapBmp.draw( blockifyMC, new flash.geom.Matrix(), flash.geom.ColorTransform() ); doColorSpaceConversionAndCompression(); } function doColorSpaceConversionAndCompression() { var blurConvF = new flash.filters.BlurFilter( curBlockSize-1, curBlockSize-1, 1 ); // first draw raw image into our rawBmp rawBmp.draw(img, new flash.geom.Matrix(), flash.geom.ColorTransform() ); // ColorMatrixFilter notes: // redResult = a[0] * srcR + a[1] * srcG + a[2] * srcB + a[3] * srcA + a[4] // greenResult = a[5] * srcR + a[6] * srcG + a[7] * srcB + a[8] * srcA + a[9] // blueResult = a[10] * srcR + a[11] * srcG + a[12] * srcB + a[13] * srcA + a[14] // alphaResult = a[15] * srcR + a[16] * srcG + a[17] * srcB + a[18] * srcA + a[19] // Do RGB Transform to YCbCr space // Y = 0.2989 R + 0.5866 G + 0.1145 B // Cb = -0.1687 R - 0.3312 G + 0.5000 B // Cr = 0.5000 R - 0.4183 G - 0.0816 B //var toYCrCbMatrix = [ // 0.2989, 0.5866, 0.1145, 0, 0, // -0.1687, -0.3312, 0.5000, 0, 127.5, // 0.5000, -0.4183, -0.0816, 0, 127.5, // 0, 0, 0, 1, 0 ]; // alpha //var toYCbCrFilter = new flash.filters.ColorMatrixFilter(toYCrCbMatrix); // Now apply filter and do transform in place om rawBmp if( justYCbCr ) { rawBmp.applyFilter( rawBmp, rct, pt, toYCbCrFilter ); } else { rawBmp.applyFilter( rawBmp, rct, pt, rgbToFilters[curFilterNum] ); } // now we have it captured, Y in R, Cb in G, Cr in B // Y in red CHANNEL, we will copy to all channels for BW viewing sepYBmp.applyFilter( rawBmp, rct, pt, theAllRedfilter ); // Cb in GREEN sepCbBmp.applyFilter( rawBmp, rct, pt, theAllGreenfilter ); if( convToggle == 1) { sepCbBmp.applyFilter( sepCbBmp, rct, pt, blurConvF ); } else if (convToggle == 2) { var pt2 = new flash.geom.Point( -curConvSize/2, -curConvSize/2 ); trace("curConvSize = "+curConvSize+" pt2.x="+pt2.x); sepCbBmp.applyFilter( sepCbBmp, rct, pt2, convf ); } if( blockToggle ) { //sepCbBmp.applyFilter( sepCbBmp, rct, pt, blockDMF ); if(testManualPixelManip) { var w = sepCbBmp.width; var h = sepCbBmp.height; trace("bitmap w="+w+" h="+h ); for(var yy=2;yy (showMapMC._x+showMapMC._width) || _ymouse (showMapMC._y+showMapMC._height) ) { reportXYOffHolder._visible = false; sourceHighlight._visible = false; targetHighlight._visible = false; return; } reportXYOffHolder._x = _xmouse; reportXYOffHolder._y = _ymouse; var myPoint = new Object; myPoint.x = _xmouse; myPoint.y = _ymouse; showMapMC.globalToLocal(myPoint); if( myPoint.x < 0 || myPoint.y > img._height) { reportXYOffHolder._visible = false; return; } else { reportXYOffHolder._visible = true; } reportXYOffHolder._alpha = 40; myPoint.x = int(myPoint.x); myPoint.y = int(myPoint.y); var mapPix = mapBmp.getPixel32( myPoint.x, myPoint.y ); var xoff = (mapPix>>8)&0xff; var yoff = mapPix&0xff; xoff = xoff-0x80; yoff = yoff-0x80; reportXYOffHolder.reportXYOff.text = "x="+myPoint.x+"y="+myPoint.y+"\nXOff= "+xoff+"\nYOff= "+yoff+"\nDMF pix=\n"+toHex(mapPix); sourceHighlight._visible = true; sourceHighlight._x = _xmouse + xoff; sourceHighlight._y = _ymouse + yoff; targetHighlight._visible = true; targetHighlight._x = _xmouse; targetHighlight._y = _ymouse; } //Number prototype to return hex values function toHex( n ){ var lowbytes = n&0x00ffffff; var temp = lowbytes.toString(16); var high = (n>>24)&0xff var temph = "00"; if( high != 0 ) { temph = high.toString(16); if(high < 16) temph = "0"+temph; } // pad out to 6 digits var curlen = temp.length; for(var t=0;t<(6-curlen);t++) { temp = "0"+temp; } // make final part temp = "0x"+temph+temp; return temp; } // ourMCLevels = 24000; infoReport.htmlText = "This illustrates Paul Bourke's 'YCC colour space and image compression'"+ " paper.http://astronomy.swin.edu.au/~pbourke/colour/ycc/"+ "\n(Experiment with block size, blur, and color space. YCrCb works well, as discussed in paper)"; // create a movie to watch the map with var showMapMC = _level0.createEmptyMovieClip("watchMap", 9999 ); showMapMC._x = 10; showMapMC._y = 550; // attach map bitmap to our watcher movie showMapMC.attachBitmap( mapBmp, 9999 ); showMapMC.onMouseMove = reportMouse; attachMovie( "TitleReportMC", "TitleBlockifier", ourMCLevels++, { _x:10, _y:(550+rct.height) } ); TitleBlockifier.title.text = "Blockifier\nDisplacement Map Explorer"; // attach a movie to report the DECODED x/y offsets from mapBmp attachMovie( "XYOffReport", "reportXYOffHolder", 20000 ); reportXYOffHolder._visible = false; createEmptyMovieClip("sourceHighlight", 21000 ); drawSquareAroundZero( sourceHighlight, 0xffaaaa ); createEmptyMovieClip("targetHighlight", 21002 ); drawSquareAroundZero( targetHighlight, 0xaaaaff ); //// createEmptyMovieClip("separationRaw", ourMCLevels++ ); rawBmp = new flash.display.BitmapData(rct.width, rct.height, false, 0x000000 ); separationRaw.attachBitmap( rawBmp, 10000 ); separationRaw._x = 250; separationRaw._y = 550; separationRaw._xscale = 100; separationRaw._yscale = 100; attachMovie( "TitleReportMC", "TitleYCbCr", ourMCLevels++, { _x:250, _y:(550+rct.height) } ); TitleYCbCr.title.text = "YCbCr (views as RGB)"; createEmptyMovieClip("separationY", ourMCLevels++ ); sepYBmp = new flash.display.BitmapData(rct.width, rct.height, false, 0x000000 ); separationY.attachBitmap( sepYBmp, 10000 ); createEmptyMovieClip("separationCb", ourMCLevels++ ); sepCbBmp = new flash.display.BitmapData(rct.width, rct.height, false, 0x000000 ); separationCb.attachBitmap( sepCbBmp, 10000 ); createEmptyMovieClip("separationCr", ourMCLevels++ ); sepCrBmp = new flash.display.BitmapData(rct.width, rct.height, false, 0x000000 ); separationCr.attachBitmap( sepCrBmp, 10000 ); separationY._x = 250; separationY._y = 260; separationY._xscale = 100; separationY._yscale = 100; attachMovie( "TitleReportMC", "TitleY", ourMCLevels++, { _x:250, _y:(260+rct.height) } ); TitleY.title.text = "Y channel"; separationCb._x = 500; separationCb._y = 260; separationCb._xscale = 100; separationCb._yscale = 100; attachMovie( "TitleReportMC", "TitleCb", ourMCLevels++, { _x:500, _y:(260+rct.height) } ); TitleCb.title.text = "Cb Channel"; separationCr._x = 750; separationCr._y = 260; separationCr._xscale = 100; separationCr._yscale = 100; attachMovie( "TitleReportMC", "TitleCr", ourMCLevels++, { _x:750, _y:(260+rct.height) } ); TitleCr.title.text = "Cr Channel"; createEmptyMovieClip("separationR", ourMCLevels++ ); sepRBmp = new flash.display.BitmapData(rct.width, rct.height, false, 0x000000 ); separationR.attachBitmap( sepRBmp, 10000 ); createEmptyMovieClip("separationG", ourMCLevels++ ); sepGBmp = new flash.display.BitmapData(rct.width, rct.height, false, 0x000000 ); separationG.attachBitmap( sepGBmp, 10000 ); createEmptyMovieClip("separationB", ourMCLevels++ ); sepBBmp = new flash.display.BitmapData(rct.width, rct.height, false, 0x000000 ); separationB.attachBitmap( sepBBmp, 10000 ); separationR._x = 250; separationR._y = 0; separationR._xscale = 100; separationR._yscale = 100; attachMovie( "TitleReportMC", "TitleR", ourMCLevels++, { _x:250, _y:(0+rct.height) } ); TitleR.title.text = "Red (decomp)"; separationG._x = 500; separationG._y = 0; separationG._xscale = 100; separationG._yscale = 100; attachMovie( "TitleReportMC", "TitleG", ourMCLevels++, { _x:500, _y:(0+rct.height) } ); TitleG.title.text = "Green (decomp)"; separationB._x = 750; separationB._y = 0; separationB._xscale = 100; separationB._yscale = 100; attachMovie( "TitleReportMC", "TitleB", ourMCLevels++, { _x:750, _y:(0+rct.height) } ); TitleB.title.text = "Blue (decomp)"; createEmptyMovieClip("separationBack", ourMCLevels++ ); rgbBmp = new flash.display.BitmapData(rct.width, rct.height, false, 0x000000 ); separationBack.attachBitmap( rgbBmp, 10000 ); separationBack._x = 0; separationBack._y = 260; separationBack._xscale = 100; separationBack._yscale = 100; attachMovie( "TitleReportMC", "TitleAfter", ourMCLevels++, { _x:0, _y:(260+rct.height) } ); TitleAfter.title.text = "After (decompressed)"; colorSpaceChange( false ); // update color space titles curBlockSize = 4; blockSizeChange( 0 ); // force block to be made and UI to be updated blockToggle = false; blockToggleChange(); // toggle block to true and update UI convToggle = 0; // convToggleChange() will change to use blur filter convToggleChange(); // force update of UI (will ADVANCE convToggle resetMapToBlockAndApply(); // make sure things are up to date function convToggleChange() { convToggle++; convToggle %= 2; // Current Conv average filter shifts image, so it sucks and blur works better if( convToggle==0 ) { convToggleReport.text = "Toggle\nBlur On"; // we are now NONE curFilterReport.text = "No Blur filter"; } else if( convToggle==1 ) { convToggleReport.text = "Toggle\nBlur Off"; // now blue curFilterReport.text = "Blur Filter size ="+(curBlockSize-1); //} else { // convToggleReport.text = "To None"; // now conv // curFilterReport.text = "Now Convolution average blocksize ="+curConvSize; } doColorSpaceConversionAndCompression(); } function colorSpaceChange( changeNum ) { if( changeNum==undefined || changeNum ) { curFilterNum++; curFilterNum %= rgbToFilters.length; } colorSpaceReport.text = titleOfFilters[curFilterNum]; TitleY.title.text = titleOfChannelsFilters[curFilterNum][0] + " channel"; TitleCb.title.text = titleOfChannelsFilters[curFilterNum][1] + " channel"; TitleCr.title.text = titleOfChannelsFilters[curFilterNum][2] + " channel"; TitleYCbCr.title.text = titleOfChannelsFilters[curFilterNum][3] + "\n(viewed as RGB)"; doColorSpaceConversionAndCompression(); } function blockToggleChange() { blockToggle = !blockToggle; if( blockToggle ) { blockToggleReport.text = "Toggle\nBlockify Off"; // we are now NONE blockFilterReport.text = "Blockify filter using DisplacementMapFilter"; } else { blockToggleReport.text = "Toggle\nBlockify On"; // now conv blockFilterReport.text = "No blockify filter"; } doColorSpaceConversionAndCompression(); } function blockSizeChange( delta ) { curBlockSize += delta; if( curBlockSize < 1 ) { curBlockSize = 1; } makeBlockStuff(curBlockSize); blockSizeReport.text = "Block Size\n"+curBlockSize+" pixels."; //+ "\nconv size ="+curConvSize; if( convToggle==1 ) { curFilterReport.text = "Now Blur size ="+(curBlockSize-1) } resetMapToBlockAndApply(); // this copies map to map explorer . doColorSpaceConversionAndCompression(); } function makeBlockStuff( blockSize ) { var blockBmp = new flash.display.BitmapData( blockSize, blockSize, false, 0x008080 ); var mid = int(blockSize/2); for(var j=0;j 5 ) blockSize = 5; curConvSize = blockSize; convMatrix = averageMatrices[curConvSize]; convf = new flash.filters.ConvolutionFilter(curConvSize, curConvSize, convMatrix, curConvSize*curConvSize ); // update file size report var w = rct.width; var h = rct.height; var yChannelSize = w*h; var blockW = Math.ceil(w/curBlockSize); var blockH = Math.ceil(h/curBlockSize); var chromChannelSize = blockW * blockH; var totalBytes = (yChannelSize + 2*chromChannelSize); var totalBits = totalBytes * 8; var bitsPerPixel = totalBits / yChannelSize; fileSizeReport.text = "Width="+w+" Height="+h+"\nY channel size="+yChannelSize+" (bytes)\n"+ "width in blocks="+blockW+"\nheight in blocks="+blockH+ "\nCr/Cb channels (2x)="+chromChannelSize+" bytes\n"+ "Total size = "+totalBytes+" bytes\n"+ "bits/pixel = "+bitsPerPixel; }