You should be using FBOs for Bitmap, no question about it.
The Bitmap class doesn't do resizing, which is the only thing FBOs limit you with, this is certainly the way you should do it.
Switching targets dozens of times in a frame is a problem, however you'd only be switching targets when processing the Bitmap. I can imagine the screen Bitmap being the only thing targeted for a static scene and things like drawing text should be done once at load-time anyway. I think it would work and it will be faster than the current Bitmap class, the only problem is compatibility, supporting anything less than OpenGL 3.0 is pushing it these days and you have plans to support OpenGL 1.1.
I really think it's worth sacrificing GL 1.1 support and going for 2.0 as a requirement, if you machine can't do GL 2.0 then you need to upgrade away from your 10 year old computer.
Here's some ideas:
Bitmap.new( width, height )
Creates an FBO with the dimensions
width and
height and attaches a colour buffer which is tied to a texture handle.
Bitmap.new( filename )
Opens up the file at
filename, reads the dimensions of the file and creates a new FBO of these dimensions. The file is then loaded and drawn to the FBO. I suggest caching the file as a GL texture with the filename and a reference count, each time you make a new bitmap from file increment the reference on the texture and when you destroy the Bitmap decrement the reference and clean up the GL texture when the reference is back down to zero, this should stop multiple GL textures being created for the same image and clean up the memory when the texture is completely absent from any Bitmap.
blit( x, y, src_bitmap, src_rect[, opacity] )
Binds the src_bitmap's FB texture handle as the input texture, targets our bitmap's FBO and renders src_bitmap's texture to the X and Y coords with opacity, using the src_bitmap's FBO width and height for sizing.
stretch_blt( dest_rect, src_bitmap, src_rect[, opacity )
Same as blt but uses rectangles to define the scaling, positioning and clipping.
fill_rect( x, y, width, height, color )
fill_rect( rect, color )
Targets our bitmap's FBO and draws a solid rectangle of color.
gradient_fill_rect( x, y, width, height, color1, color2[, vertical] )
gradient_fill_rect( rect, color1, color2[, vertical] )
Like fill_rect, but the vertices of the rectangle are set to either color1 or color2 depending on if vertical is true or false.
clear
Clears the FBO's colour attachment
clear_rect( x, y, width, height )
clear_rect( rect )
Does fill_rect with a colour of transparent
get_pixel( x, y )
glReadPixels!
set_pixel( x, y, color )
glDrawPixels!
hue_change( hue )
This is the hard one. I would use a large FBO that is global to all Bitmap instances (static in C++) and draw the Bitmaps's FBO to that and record the size of it, I'd then target the Bitmap's FBO and draw back to the Bitmap's FBO with either a hue changing shader.
The alternative is do what RGSS already does; read pixel, write pixel (SLOW!). I guess you have no choice with fixed-function GL.
blur
Same technique as above with a blur shader, or fallback to read pixel, write pixel if no fixed-function GL.
Because blur shaders are 2-pass, there should be two large global (static) FBOs for Bitmap classes, one for rendering pass 1 and one for rendering pass 2.
radial_blur( angle, division )
Same method as blur, but instead of a blur shader you rotate the result each time. division defines how many passes are done between the two static FBOs and angle (Between 0 and 360) is the strength of the radial blur.
draw_text( x, y, width, height, str[, align] )
draw_text( rect, str[, align] )
Hopefully you have a font rendering system in place
Target Bitmap's FBO and draw font to it.
text_size( str )
Returns how big the text will be if it is rendered to the FBO (If the str doesn't fit on the FBO, the text is reduced in size, this returns the reduction amount).
EDIT: If you didn't get the picture from what I am saying; Don't think of doing blitting with OpenGL, it's a waste of time and you might as-well stick with GDI+, draw rectangles, it achieves the same effect and actually uses features of your GPU beyond having it as secondary RAM that requires locking for a slow copy to CPU memory for editing