That can be done with transforms (like OpenGL does for example) to your viewport coordinates. It's just 2D geometry math.
Zoom is essentially a division. A 200x200 viewport with 2x zoom is actually 100x100 size in your texture (which will be expanded to fill the 200x200 viewport, effectively zooming.) After that, you just have to apply a translation to offset your zoomed area, which is a simple sum.
If you use the same zoom for both X and Y coordinates, you'll keep the aspect ratio the same. Here's some pseudocode for zooming into a full-screen texture (hence the
backgroundX = windowX/zoom + translationX;
backgroundY = windowY/zoom + translationY;
Bear in mind you need some kind of interpolation (graphic cards can do it for you) because 1 pixel without zoom will be split into two half-pixels with 2x zoom (1px/2 = 0.5px size each fragment.) If you use nearest neighbor for interpolation, the zoomed image will be pixelated (which may be the effect you intend.)
I always implemented this myself on shaders. Here is a section of a shader of mine that zooms a texture applied to a full-screen quad, in case it helps:
uniform ivec2 resolution; // board size (for normalization)
uniform ivec3 camera; // xy = offset, z = zoom
vec2 p = (gl_FragCoord.xy/resolution - vec2(0.5))/camera.z + camera.xy/(float)resolution;
p holds my texture coordinate (in [0..1] range, that's why I need the texture resolution) for the fragment at
gl_FragCoord in screen. If you're not drawing a full-screen quad, you have to replace
gl_FragCoord with your texture coordinate (passed as a uniform or with
glTexCoord in fixed-pipeline OpenGL.)
I substract 0.5 from the fragment coordinate to center the zoom direction on screen (otherwise you would zoom into the bottom-left corner.) You don't need this, since you could also add
resolution/2 to your camera offset in your application, but I prefer using the center of the screen as the origin (for convenience.)