There are several problems with your image generator:
1. Since the bounds of the image is modified by the rotation, when you call drawImage(source, 0, 0, null) for instance, you can no longer expect the top-left corner of the rotated source image to be at (0, 0) on the destination. Imagine it this way: A plain rectangle image stamped at (20, 0) and then rotated 45 degrees - you get an image whose minimum bounds are less than (20, 0).
Draw the current rotation onto an intermediate image first, then stamp that where you want it to be on the destination.
public BufferedImage generateRotatedImages(BufferedImage sourceImage, int numFrames)
BufferedImage image = graphicsConfiguration.createCompatibleImage(sourceImage.getWidth()
* numFrames, sourceImage.getHeight(), Transparency.BITMASK);
BufferedImage rotatedSource = graphicsConfiguration.createCompatibleImage(sourceImage.getWidth(),
Graphics2D g2d = image.createGraphics();
int sourceWidth = sourceImage.getWidth();
int sourceHeight = sourceImage.getHeight();
double angleBetweenFrames = 2 * Math.PI / numFrames;
int imageIndex = 0;
for (double a = 0; a < 2 * Math.PI; a += angleBetweenFrames)
Graphics2D rsG = rotatedSource.createGraphics();
rsG.rotate(a, rotatedSource.getWidth()/2, rotatedSource.getHeight()/2);
rsG.drawImage(sourceImage, (rotatedSource.getWidth() - sourceWidth)/2, (rotatedSource.getHeight() - sourceHeight)/2, null);
g2d.drawImage(rotatedSource, imageIndex * sourceWidth, 0, null);
rsG.fillRect(0, 0, rotatedSource.getWidth(), rotatedSource.getHeight());
2. You would still get clipping when the square image is rotated and drawn onto a BufferedImage that is smaller than the size of the rotated image. Imagine a square image that is again rotated 45 degrees (to form a diamond shape) - the bounds of that rotated image is larger than the original, and when drawn onto a destination which is the same size as the square before rotation would result in clipping.
Add a bit of transparent buffer around your sprite image so that the sprite graphic you're interested in doesn't get clipped.
3. You can also consider calling repaint() (as well as revalidate()) on your ImageCanvas whenever the frames are generated. Currently, for 1-4 frames of animation, the ImageCanvas is not updated to draw the generated image.