Three vertices, three coordinate pairs

Texture coordinate triangle

Texture coordinates are a vertex attribute, exactly like position or colour — each vertex carries one (s,t) pair, and the rasteriser interpolates between them across the triangle. Drag the handles on either side: the left triangle picks a region out of the image, the right one says where it lands. The code underneath is what you would have written.

In the texture — (s,t) source region
Drag a handle
vertex 0 vertex 1 vertex 2
On the surface — (x,y) destination
These handles move too

The image region is stretched to fit whatever shape you give it. Nothing forces the two triangles to be the same shape — that mismatch is the distortion.

The code live

      

Note the ordering: glTexCoord2d sets a piece of current state, and the following glVertex2d consumes it. Call them the other way round and the vertex picks up the previous vertex's coordinates.

Source area (of the image)
Magnification
Orientation
Interpolated (s,t) at pointer
Why the pointer readout matters. Move over the right-hand triangle and watch the coordinate readout change smoothly. Nobody stored those values — only three pairs were ever specified, at the corners. Everything between them is produced by the rasteriser interpolating the attribute across the face, which is precisely what the varying vec2 v_texCoords in a modern shader is: a per-vertex value that arrives at the fragment shader already interpolated.