/** flips individual blocks within a bitmap. Specifically used for doing sprite sheets. But I am sure you could find another use if you wanted. * @param inBM * @param spritePixelWidth * @param spritePixelHeight * @param inTransformType :An enum from TransFiveConstants. Example: TransFiveConstants.VERTICAL_FLIP * @return a bitmap data object where all the sprites within the sprite sheet bitmap have been rotated or transformed **/ public static function makeTransformedCopyOfSpriteSheet(inSpriteSheet:BitmapData, spritePixelWidth:int, spritePixelHeight:int, inTransformType:int):BitmapData { //Do error check to make sure we evenly fit into the bitmap we are doing transforms on. CONFIG::debug{ if ( (inSpriteSheet.width % spritePixelWidth ) != 0) { throw new Error("input error: width does not go in evenly! Fix it."); } if ( (inSpriteSheet.height % spritePixelHeight) != 0) { throw new Error("input error: height does not go in evenly! Fix it."); } }//CONFIG_debug //Calculate width and height in sprites of the inSpriteSheet. var widthInSprites :int = inSpriteSheet.width / spritePixelWidth ; var heightInSprites:int = inSpriteSheet.height / spritePixelHeight; /** Bitmap that takes rectangle chunks out of inSpriteSheet, one at a time. **/ var inBM:BitmapData = new BitmapData(spritePixelWidth, spritePixelHeight, true, 0x00); //Inlined copy of code in makeTransformedCopy //////////////////////////////////////////////////////////////////// var flipWidthHeight:Boolean = false; if (inTransformType == TransFiveConstants.ROTATE_NEG_90 || inTransformType == TransFiveConstants.ROTATE_POS_90 ) { flipWidthHeight = true; } var outWID:int = (flipWidthHeight ? inBM.height : inBM.width ); var outHGT:int = (flipWidthHeight ? inBM.width : inBM.height); //////////////////////////////////////////////////////////////////// /** Bitmap that is a [rotated||transformed] version of inputBitmap: **/ var outBM:BitmapData = new BitmapData(outWID, outHGT, true, 0x00); var outputSpriteSheetWidth :int = outBM.width * widthInSprites; var outputSpriteSheetHeight:int = outBM.height * heightInSprites; /** The output of this function **/ var outputSpriteSheet :BitmapData = new BitmapData(outputSpriteSheetWidth, outputSpriteSheetHeight, true, 0x00); //scan through the sheet with a rectangle and make all the transformed copies you need. //Every time you make a transformed chunk/copy, move it from the inSpriteSheet to the outputSpriteSheet /** Places the [rotated||transformed] chunk in correct spot on outputSpriteSheet **/ var finalDestination:Point = new Point(); var cookieCutter:Rectangle = new Rectangle(); cookieCutter.width = spritePixelWidth ; cookieCutter.height = spritePixelHeight; for (var xx:int = 0; xx < widthInSprites ; ++xx){ for (var yy:int = 0; yy < heightInSprites; ++yy){ cookieCutter.x = xx * spritePixelWidth; cookieCutter.y = yy * spritePixelHeight; //Cut chunk out of main sprite sheet: inBM.copyPixels(inSpriteSheet, cookieCutter, ZZ, null, null, true); //Transform the chunk you cut out of the main sprite sheet: makeTransformedCopy(inBM, inTransformType, outBM); //Paste the transformed copy into the output sheet: finalDestination.x = xx * outBM.width; //if outBM is rotated, this width will NOT BE SAME AS spritePixelWidth finalDestination.y = yy * outBM.height; outputSpriteSheet.copyPixels(outBM, outBM.rect, finalDestination, null, null, true); }}//next [xx, yy] return outputSpriteSheet; }//makeTransformedCopyOfSpriteSheet /** Flips/Mirrors and Rotates using a 1D index scan remap. "transformUsingScanRemap" * * Meaning, I put data into a 1D array, then change my assumptions on the "scan order" ( left-right, then top-bottom is default convention), * I can effectively rotate or transform the pixel input. * * All we have to do is pack the bitmap into a 1D array. (scanning left-right, top-to-bottom) * And then convert it BACK to the 2D array using a different formula assuming the data is packed via a different scan order. * * EXAMPLE: * If we change our assumption to assume it is left-right, then BOTTOM-to-top, and make a formula for mapping 1D indexes * to 2D values based on that scan order assumption, we achieve a vertical flip. * * @param inBM :Bitmap we are making a transformed copy of. * @param inTransformType :The enum for the type of [transform||rotation] to use. For values check TransFiveConstants.as * @param outputBitmap :Supply this to OVERWRITE an existing bitmap instead of create a NEW bitmap for output. * @return a transformed or rotated bitmap **/ public static function makeTransformedCopy(inBM:BitmapData, inTransformType:int, outputBitmap:BitmapData = null):BitmapData { //If the bitmap is being ROTATED, we will have to flip the output width and height. var flipWidthHeight:Boolean = false; if (inTransformType == TransFiveConstants.ROTATE_NEG_90 || inTransformType == TransFiveConstants.ROTATE_POS_90 ) { flipWidthHeight = true; } var outWID:int = (flipWidthHeight ? inBM.height : inBM.width ); var outHGT:int = (flipWidthHeight ? inBM.width : inBM.height); //You can supply a reference to the OUTPUT of this function if you are doing some type of batch processing //And want to avoid repetitively constructing new intermediary bitmaps: if (outputBitmap == null) { var outputBitmap:BitmapData = new BitmapData(outWID, outHGT, true, 0x00); } else { if (outputBitmap.width != outWID) { ICU.error("Bad output bitmap supplied. Size is wrong."); } if (outputBitmap.height != outHGT) { ICU.error("Bad output bitmap supplied. Size is wrong."); } } /** Max x index when remapping 1D values. **/ var maxXXX:int = outWID - 1; /** Max y index when remapping 1D values. **/ var YYYmax:int = outHGT - 1; /** Number of full columns, using 1D scan order for this orientation. **/ var fullColumns:int = 0; /** Number of full rows, using 1D scan order for orientation specified. **/ var fullRows:int = 0; /**What is left over after we have calculated the rows or collumns we have. **/ var remainder:int = 0; var curPix:uint; var index:int = ( -1); var trans:IntPoint = new IntPoint(); inBM.lock(); outputBitmap.lock(); for (var yy:int = 0; yy < inBM.height; yy++) { for (var xx:int = 0; xx < inBM.width ; xx++) { ++index; //using 1D index position, remap that to correct 2D position. //To do different transforms and rotations, simply change your assumptions on how we //map from 1D to 2D. //Standard 1D to 2D assumes scan lines go left-right, then top-bottom. Row by row. //2D to 1D formula for standard: //1D = (Y * width) + X. //how many full widths you can get out of 1D == Y. Remainder == X. //2D.Y = (1D/width); //2D.X = 1D - (2D.Y * width); if (inTransformType == TransFiveConstants.NO_TRANSFORM) { //[1][2] Assumed scan order (AKA 1D to 2D Mapping) fullRows = (index / outWID); //[3][4] used to get full remainder = index - (fullRows * outWID); //[5][6] rows and remainder. trans.iy = fullRows; trans.ix = remainder; }else if (inTransformType == TransFiveConstants.VERTICAL_FLIP) { //[5][6] Assumed scan order (AKA 1D to 2D Mapping) fullRows = (index / outWID); //[3][4] used to get full remainder = index - (fullRows * outWID); //[1][2] rows and remainder. trans.iy = YYYmax - fullRows; trans.ix = remainder; }else if (inTransformType == TransFiveConstants.ROTATE_NEG_90) { //[2][4][6] Assumed scan order (AKA 1D to 2D Mapping) fullColumns = (index / outHGT); //[1][3][5] used to get full rows and remainder. remainder = index - (fullColumns * outHGT); trans.ix = fullColumns; trans.iy = YYYmax - remainder; }else if (inTransformType == TransFiveConstants.ROTATE_POS_90) { //[5][3][1] Assumed scan order (AKA 1D to 2D Mapping) fullColumns = (index / outHGT); //[6][4][2] used to get full rows and remainder. remainder = index - (fullColumns * outHGT); trans.ix = maxXXX - fullColumns; trans.iy = remainder; }else if (inTransformType == TransFiveConstants.FLOOR_MIRROR) { //[2][1] Assumed scan order (AKA 1D to 2D Mapping) fullRows = (index / outWID); //[4][3] used to get full remainder = index - (fullRows * outWID); //[6][5] rows and remainder. trans.iy = fullRows; trans.ix = maxXXX - remainder; } else { throw new Error("Transform type not recognized"); } //Copy and paste the pixel now that we know where to put it: curPix = inBM.getPixel32(xx, yy); outputBitmap.setPixel32(trans.ix, trans.iy, curPix); }}//next [xx, yy] inBM.unlock(); outputBitmap.unlock(); return outputBitmap; }//transformUsingScanRemap