Fix sprite sheet "frame drift" on different densities?

All your problems with Audio, Video and Images.

Fix sprite sheet "frame drift" on different densities?

Postby bignobody » Sat Oct 09, 2010 8:02 pm

Hello, this is my first post on this forum. I'm not new to game development, but am new to Android development.

Android seems to take care of different screen resolutions and densities, unless you're inheriting from SurfaceView and implementing SurfaceHolder.Callback (which you need to do if you want any kind of rendering performance). It seems in this case, you need to handle the scaling yourself.

I've attempted to do this, and for the most part it works. However, I've implemented my own sprite sheet class, and at smaller or larger pixel densities (0.75 or 1.5) I'm getting a frame "drift" between frames (each frame of animation is increasingly off by about one pixel), which is probably due to the scaling.

I'll post some code here, in hopes someone will notice if I'm doing something wrong...

Syntax: [ Download ] [ Hide ]
Using java Syntax Highlighting
  1. // ## inside my resources container class initialization
  2.  
  3. // Get the display metrics
  4. WindowManager winManager = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
  5. systemDisplay = winManager.getDefaultDisplay();
  6. displayMetrics = new DisplayMetrics();
  7. systemDisplay.getMetrics(displayMetrics);
  8.  
  9. // load and scale the background image to fit on the screen
  10. Bitmap workBitmap = BitmapFactory.decodeResource(res, R.drawable.boardscreen);
  11. Matrix scaleMatrix = new Matrix();
  12. int originalSize = workBitmap.getWidth();
  13. int newSize = displayMetrics.heightPixels;
  14.  
  15. if (newSize > displayMetrics.widthPixels)
  16. {
  17.     // we want the smaller of the two
  18.     newSize = displayMetrics.widthPixels;
  19. }
  20.                
  21. ScaleBy = ((float)newSize) / originalSize;
  22.                
  23. scaleMatrix.postScale(ScaleBy, ScaleBy);
  24.  
  25. // create the scaled bitmap for use            
  26. BitmapCollection[0] = Bitmap.createBitmap(workBitmap, 0, 0, originalSize, originalSize, scaleMatrix, true);
  27. workBitmap.recycle();
  28. workBitmap = null;
  29.  
  30. // Scale the sprite sheet resource by the same factor so it is proportional to the background
  31. workBitmap = BitmapFactory.decodeResource(res, R.drawable.spritesheet);
  32. originalSize = workBitmap.getWidth();
  33. BitmapCollection[1] = Bitmap.createBitmap(workBitmap, 0, 0, originalSize, originalSize, scaleMatrix, true);
  34. workBitmap.recycle();
  35. workBitmap = null;
  36.  
  37.  
  38. // Calculate the sprite frame sizes and screen starting positions, taking in to account ScaleBy and density
  39.  
  40. SPRITE_SIZE = (int) ((64.0f * ScaleBy) * displayMetrics.density); // original sprite frames are 64x64 pixels
  41. GRID_START_X = (int) (((float) GRID_START_X * ScaleBy) * displayMetrics.density); //GRID_START_n is 20 pixels
  42. GRID_START_Y = (int) (((float) GRID_START_Y * ScaleBy) * displayMetrics.density);
  43.  
  44. // create one of our custom sprites, position it on screen, and start the animation
  45. GameSprites[0] = new Sprite(context,1,SPRITE_SIZE,SPRITE_SIZE,0);
  46. GameSprites[0].SetPosition(GRID_START_X,GRID_START_Y);
  47. GameSprites[0].DoAnimation(0);
  48.  
Parsed in 0.037 seconds, using GeSHi 1.0.8.4


This all seems to work well (background scaling, sprite positioning) on all resolutions except for the actual animation drift.

Syntax: [ Download ] [ Hide ]
Using java Syntax Highlighting
  1. // ## code from inside my Sprite sheet class
  2.  
  3. // constructor
  4. public Sprite(Context context, int resourceIndex, int frameWidth, int frameHeight, int initialFrame)
  5. {
  6.         width = frameWidth;
  7.         height = frameHeight;
  8.         xPos = 0;
  9.         yPos = 0;
  10.         currentFrame = 0;
  11.         animationFrame = 0;
  12.         frameRect = new Rect(0,0,width,height);
  13.         isAnimating = false;
  14.  
  15.         if (loadSpriteResource(resourceIndex, context))
  16.         {
  17.                 SetToFrame(initialFrame);
  18.         }
  19.                
  20. }
  21.        
  22. private boolean loadSpriteResource(int resourceIndex, Context context)
  23. {
  24.                
  25.         if (GameToyBox.BitmapCollection[resourceIndex] == null)
  26.         {
  27.                 return false;
  28.         }
  29.         bitmapIndex = resourceIndex;
  30.                //GameToyBox is my static resource container class
  31.         bitmapWidth = GameToyBox.BitmapCollection[bitmapIndex].getWidth();
  32.         bitmapHeight = GameToyBox.BitmapCollection[bitmapIndex].getHeight();
  33.         framesAcross = bitmapWidth/width;
  34.         framesDown = bitmapHeight/height;
  35.         maxFrame = framesAcross * framesDown;
  36.         return true;
  37. }
  38.  
  39. // SetToFrame is also called by the animation system to set the sprite sheet to it's current frame
  40.  
  41. public void SetToFrame(int frame)
  42. {
  43.         currentFrame = frame;
  44.        
  45.               // find the starting x/y positions for this frame
  46.         int frameX = currentFrame * width;
  47.         int frameY = 0;
  48.         int xWidth = width * framesAcross;
  49.        
  50.               // sprite sheet is laid out left to right, top to bottom
  51.         while (frameX >= xWidth)
  52.         {
  53.                 frameY += height;
  54.                 frameX -= xWidth;
  55.         }
  56.                
  57.  
  58.               // set the new frameRect (used in the Canvas.drawBitmap call)            
  59.         frameRect.left = frameX;
  60.         frameRect.top = frameY;
  61.         frameRect.bottom = frameY + height;
  62.         frameRect.right = frameX + width;
  63. }
  64.  
  65.  
Parsed in 0.036 seconds, using GeSHi 1.0.8.4


Thanks for reading. If you have any ideas on how to fix this issue, I'd love to hear it.

Cheers,

-bignobody
User avatar
bignobody
Freshman
Freshman
 
Posts: 3
Joined: Sat Oct 09, 2010 6:41 pm
Location: Canada

Top

Re: Fix sprite sheet "frame drift" on different densities?

Postby bignobody » Mon Oct 11, 2010 5:01 pm

Wasted quite a few hours trying to implement a work around. Basically scaling each frame of animation individually at run time and then rebuilding the sprite sheet in memory. It has the same problem.

None of the Android sprite sheet tutorials I've read mention anything about dealing with different pixel densities, but surely I'm not the only one who's encountered this issue? There must be a solution for it...
User avatar
bignobody
Freshman
Freshman
 
Posts: 3
Joined: Sat Oct 09, 2010 6:41 pm
Location: Canada

Fixed: Fix sprite sheet "frame drift" on different densities

Postby bignobody » Mon Oct 11, 2010 6:52 pm

Ha. Seems my workaround actually does work - I had forgotten some earlier experimental code in my sprite class that was throwing things off.

The only downside I can see is that it takes longer than I'd like to execute (at least, under emulation - I'm not sure if the performance will be better on a real device).

In the interest of sharing with the community, here's my solution:

Syntax: [ Download ] [ Hide ]
Using java Syntax Highlighting
  1. // ## inside my resource container class
  2.  
  3. // force the decodeResources to leave the pixel density at 1, regardless of target density
  4. BitmapFactory.Options ops = new BitmapFactory.Options();
  5. ops.inDensity = 1;
  6. ops.inTargetDensity = 1;
  7. ops.inScreenDensity = 1;
  8. ops.inScaled = false;
  9.        
  10. // load the background image           
  11. Bitmap workBitmap = BitmapFactory.decodeResource(res, R.drawable.boardscreen, ops);
  12.  
  13. Matrix scaleMatrix = new Matrix();
  14. int originalSize = workBitmap.getWidth();
  15. int newSize = displayMetrics.heightPixels;
  16. if (newSize > displayMetrics.widthPixels)
  17. {
  18.         // we want the smaller of the two
  19.         newSize = displayMetrics.widthPixels;
  20. }
  21.  
  22. // figure out how much to scale the background by to fit on the screen         
  23. ScaleBy = ((float)newSize) / originalSize;
  24. scaleMatrix.postScale(ScaleBy, ScaleBy);
  25.  
  26. // scale the background image to fit the screen
  27. BitmapCollection[0] = Bitmap.createBitmap(workBitmap, 0, 0, originalSize, originalSize, scaleMatrix, true);
  28.  
  29. workBitmap.recycle();
  30. workBitmap = null;
  31.  
  32. // calculate the size the sprites should be to be proportional to the background
  33. SPRITE_SIZE = (int) (64.0f  * ScaleBy);  // sprite frames are 64 x 64 pixels
  34.  
  35. // load the sprite sheet bitmap        
  36. workBitmap = BitmapFactory.decodeResource(res, R.raw.stones, ops);
  37.  
  38. // rebuild each frame of the sprite sheet at the new scale
  39. BitmapCollection[1] = scaleAndRebuildSpriteSheet(workBitmap, 8, 8, 64, SPRITE_SIZE);
  40. workBitmap.recycle();
  41. workBitmap = null;
  42.  
  43.  
  44. static public Bitmap scaleAndRebuildSpriteSheet(Bitmap originalBitmap, int framesAcross, int framesDown, int frameSize, int newFrameSize)
  45. {
  46.         // Create bitmap to hold the new scaled sprite sheet
  47.         Bitmap scaledSpriteSheet = Bitmap.createBitmap(framesAcross * newFrameSize, framesDown * newFrameSize, originalBitmap.getConfig());
  48.         // Create a work bitmap to hold the individual frames
  49.         Bitmap frameBitmap = Bitmap.createBitmap(frameSize, frameSize, originalBitmap.getConfig());
  50.         // bitmap to hold a scaled down frame
  51.         Bitmap scaledFrameBitmap = null;
  52.         // pixel buffer to hold contents of original sprite sheet
  53.         int pixelBuffer[] = new int[(framesAcross * frameSize) * (framesDown * frameSize)];
  54.         // pixel buffer for a single frame
  55.         int pixelFrameBuffer[] = new int[frameSize * frameSize];
  56.         // pixel buffer for a scaled down frame
  57.         int scaledFramePixelBuffer[] = new int[newFrameSize * newFrameSize];
  58.         // final pixel buffer to write each scaled down frame to
  59.         int scaledPixelBuffer[] = new int[(framesAcross * newFrameSize) * (framesDown * newFrameSize)];
  60.  
  61.         int originalBitmapWidth = originalBitmap.getWidth();
  62.         int newSpriteSheetWidth = scaledSpriteSheet.getWidth();
  63.                
  64.         // Create scale matrix
  65.         Matrix scaleMatrix = new Matrix();
  66.         float scaleFactor = ((float)newFrameSize) / frameSize;
  67.         scaleMatrix.postScale(scaleFactor, scaleFactor);
  68.  
  69.         // buffer all the pixels from the original sprite sheet
  70.         originalBitmap.getPixels(pixelBuffer,0,originalBitmapWidth, 0, 0, originalBitmapWidth, originalBitmap.getHeight());
  71.  
  72.  
  73.                
  74.         // iterate through each frame in the sprite sheet
  75.         for (int y = 0; y < framesDown; y ++)
  76.         {
  77.                 for (int x = 0; x < framesAcross; x ++)
  78.                 {
  79.  
  80.                         // copy the pixels for the current frame to the frame pixel buffer
  81.                         int pdx = 0;
  82.                         int sdx = 0;
  83.                         for (int yp = 0; yp < frameSize; yp ++)
  84.                         {
  85.                                 for (int xp = 0; xp < frameSize; xp ++)
  86.                                 {
  87.                                         sdx = (xp + (x * frameSize)) + ((originalBitmapWidth) * yp) + (originalBitmapWidth * (frameSize * y) );
  88.                                         pixelFrameBuffer[pdx] = pixelBuffer[sdx];
  89.                                         pdx ++;
  90.                                 }
  91.                         }
  92.                                
  93.                         // write those pixels to the frameBitmap
  94.                         frameBitmap.setPixels(pixelFrameBuffer, 0, frameSize, 0, 0, frameSize, frameSize);
  95.                                
  96.                         // scale the frame down to the new size we want
  97.                         scaledFrameBitmap = Bitmap.createBitmap(frameBitmap, 0, 0, frameSize, frameSize, scaleMatrix, true);
  98.                                
  99.                         // Buffer the scaled frame pixels
  100.                         scaledFrameBitmap.getPixels(scaledFramePixelBuffer,0,newFrameSize, 0, 0, newFrameSize, newFrameSize);
  101.                                
  102.                         // copy this frame to the final pixel buffer
  103.                         for (int yp = 0; yp < newFrameSize; yp ++)
  104.                         {
  105.                                 for (int xp = 0; xp < newFrameSize; xp ++)
  106.                                 {
  107.                                         sdx = xp + (newFrameSize * yp);
  108.                                         pdx = (xp + (x * newFrameSize)) + ((newSpriteSheetWidth) * yp) + (newSpriteSheetWidth * (newFrameSize * y) );
  109.                                         scaledPixelBuffer[pdx] = scaledFramePixelBuffer[sdx];
  110.                                 }
  111.                         }
  112.                                
  113.                         scaledFrameBitmap.recycle();
  114.                         scaledFrameBitmap = null;
  115.  
  116.                 }
  117.         }
  118.  
  119.         // write the final pixel buffer to the final bitmap
  120.         scaledSpriteSheet.setPixels(scaledPixelBuffer, 0, newSpriteSheetWidth, 0, 0, newSpriteSheetWidth, scaledSpriteSheet.getHeight());
  121.  
  122.         return scaledSpriteSheet;
  123.  
  124. }
  125.  
  126.  
Parsed in 0.050 seconds, using GeSHi 1.0.8.4


Hopefully this will be helpful to others. Also, if you notice any way to get any speed increases out of the above code, I'd be interested in hearing about that too :)
User avatar
bignobody
Freshman
Freshman
 
Posts: 3
Joined: Sat Oct 09, 2010 6:41 pm
Location: Canada

Top

Return to Multimedia Problems

Who is online

Users browsing this forum: No registered users and 9 guests