Skip to Content
All memories

2D Graphics Deep Dive - Drawing, Transforming, and Viewing Your Digital World

 — #Computer Graphics#CS Basics

Hey there, pixel pioneers and digital dreamers! In our previous explorations, we've laid down the groundwork of Computer Graphics, understanding what it is, where it's used, and the basics of how images appear on our screens. Now, it's time to roll up our sleeves and get into the nitty-gritty of creating and manipulating graphics in a two-dimensional space.

Ever wondered how your computer flawlessly draws a perfectly straight line or a smooth circle on a screen made of square pixels? Or how game characters zoom, spin, and morph with such fluidity? And how does a program decide which part of a larger scene to display in its window? Today, we're tackling exactly these questions! We'll journey through 2D output primitives (the basic shapes), geometric transformations (moving and changing those shapes), and the 2D viewing pipeline (how we frame our scene). Get ready for some fascinating algorithms and a little bit of (fun!) math!


Part 1: Drawing the Unseen - 2D Output Primitives

Output primitives are the fundamental geometric structures we can instruct a graphics system to draw. In 2D, these are the dots, lines, curves, and filled areas that form the visual basis of everything else. Since our screens are grids of discrete pixels, drawing even a simple line isn't as straightforward as it sounds!

Lines - The Straight Story

A line is defined by two endpoints. The challenge is to determine which pixels on the screen should be illuminated to best approximate this ideal straight line.

  1. Digital Differential Analyzer (DDA) Algorithm: This is an intuitive algorithm that uses the line equation y=mx+cy = mx + c. Given two endpoints (x1,y1)(x_1, y_1) and (x2,y2)(x_2, y_2), we calculate the slope m=y2y1x2x1=ΔyΔxm = \frac{y_2 - y_1}{x_2 - x_1} = \frac{\Delta y}{\Delta x}.

    • If m1|m| \le 1, we increment xx by 1 and calculate yk+1=yk+my_{k+1} = y_k + m.
    • If m>1|m| > 1, we increment yy by 1 and calculate xk+1=xk+1mx_{k+1} = x_k + \frac{1}{m}.

    The calculated xx or yy values are real numbers and need to be rounded to the nearest integer to pick the pixel.

    • Pros: Simple to understand.
    • Cons: Uses floating-point arithmetic (slower) and rounding, which can accumulate errors.
  2. Bresenham's Line Algorithm: A highly efficient and popular algorithm that uses only integer arithmetic! It determines the closest pixel to the ideal line by examining a "decision parameter." For a line with slope 0<m<10 < m < 1: Start with (x0,y0)(x_0, y_0). At each step xkx_k, we decide whether yk+1y_{k+1} should be yky_k or yk+1y_k + 1. The decision parameter pkp_k is initialized as p0=2ΔyΔxp_0 = 2\Delta y - \Delta x. At each step kk:

    • If pk<0p_k < 0, the next point is (xk+1,yk)(x_k + 1, y_k), and pk+1=pk+2Δyp_{k+1} = p_k + 2\Delta y.
    • Else, the next point is (xk+1,yk+1)(x_k + 1, y_k + 1), and pk+1=pk+2Δy2Δxp_{k+1} = p_k + 2\Delta y - 2\Delta x.

    This process repeats Δx\Delta x times. Similar logic applies to other slopes by considering symmetry.

    • Pros: Fast, uses only integer addition, subtraction, and bit-shifting (for multiplication by 2). Accurate.
    • Cons: A bit more complex to derive initially.

Circles - Round and Round We Go

A circle is defined by its center (xc,yc)(x_c, y_c) and radius RR. The equation is (xxc)2+(yyc)2=R2(x - x_c)^2 + (y - y_c)^2 = R^2. We can exploit the circle's eight-way symmetry: if we calculate a point (x,y)(x,y) in one octant, we can easily find 7 other points.

Midpoint Circle Algorithm (similar to Bresenham's): This algorithm also uses a decision parameter to select the closer pixel to the ideal circle. We only need to calculate pixels for one octant (e.g., from x=0x=0 to x=yx=y). Consider a circle centered at the origin. Start at (0,R)(0, R). At each step, we increment xx and decide whether yy should remain the same or decrease. The decision parameter pkp_k is initialized as p0=54Rp_0 = \frac{5}{4} - R (or 1R1-R if using integer arithmetic and starting from p0=1Rp_0 = 1-R). At each step kk, starting with (x0,y0)=(0,R)(x_0, y_0) = (0,R):

  • Plot (xk,yk)(x_k, y_k) and its symmetric points.
  • If pk<0p_k < 0, the next point is (xk+1,yk)(x_k + 1, y_k), and pk+1=pk+2xk+1+1p_{k+1} = p_k + 2x_{k+1} + 1.
  • Else, the next point is (xk+1,yk1)(x_k + 1, y_k - 1), and pk+1=pk+2xk+1+12yk+1p_{k+1} = p_k + 2x_{k+1} + 1 - 2y_{k+1}.

Repeat until xyx \ge y.

Ellipses - The Elegant Squish

An ellipse is defined by its center (xc,yc)(x_c, y_c) and two radii, rxr_x (semi-major axis) and ryr_y (semi-minor axis). The equation for an ellipse centered at the origin is x2rx2+y2ry2=1\frac{x^2}{r_x^2} + \frac{y^2}{r_y^2} = 1. Ellipses have four-way symmetry. The Midpoint Ellipse Algorithm is similar to the Midpoint Circle Algorithm but a bit more complex because the slope changes, requiring division of the ellipse into two regions where decisions are made differently.

Filling It In - Area Primitives

Drawing outlines is great, but often we want to fill shapes with color or patterns.

  1. Scan-Line Polygon Fill Algorithm: This is a very common algorithm for filling polygons.

    • For each scan line (horizontal pixel row) that crosses the polygon:
      1. Find the intersections of the scan line with the polygon edges.
      2. Sort these intersections by their x-coordinates.
      3. Fill the pixels between pairs of intersections (e.g., 1st to 2nd, 3rd to 4th, etc.), using parity rules.
    • Efficiency is improved using an "edge table" (ET) and an "active edge list" (AEL).
  2. Boundary-Fill Algorithm: Starts from an interior point (x,y)(x,y) inside the polygon.

    • If the current pixel (x,y)(x,y) is not the boundary color and not already filled:
      1. Fill the current pixel with the desired fill color.
      2. Recursively call boundary-fill for its neighbors (4-connected or 8-connected).
    • Pros: Simple concept.
    • Cons: Recursive, can lead to stack overflow for large areas. Requires a single boundary color.
  3. Flood-Fill Algorithm: Similar to boundary-fill, but instead of checking for a boundary color, it checks if the current pixel is of a specific interior "old" color.

    • If the current pixel (x,y)(x,y) is the "old" color:
      1. Change its color to the new "fill" color.
      2. Recursively call flood-fill for its neighbors.
    • Useful for changing a region of a single color to another.

Part 2: Shape Shifters - 2D Geometric Transformations

Once we can draw shapes, we want to move, resize, and rotate them! These operations are called geometric transformations. They change the coordinates describing an object.

The Basic Trio: Translation, Scaling, Rotation

  1. Translation: Moving an object without changing its shape or orientation. If a point P=(x,y)P=(x,y) is translated by (tx,ty)(t_x, t_y) to P=(x,y)P'=(x',y'), then: x=x+txx' = x + t_x y=y+tyy' = y + t_y In matrix form (using homogeneous coordinates, which we'll see soon): P=TP    [xy1]=[10tx01ty001][xy1]P' = T \cdot P \implies \begin{bmatrix} x' \\ y' \\ 1 \end{bmatrix} = \begin{bmatrix} 1 & 0 & t_x \\ 0 & 1 & t_y \\ 0 & 0 & 1 \end{bmatrix} \begin{bmatrix} x \\ y \\ 1 \end{bmatrix}

  2. Scaling: Changing the size of an object. If a point P=(x,y)P=(x,y) is scaled by factors (sx,sy)(s_x, s_y) relative to the origin to P=(x,y)P'=(x',y'), then: x=xsxx' = x \cdot s_x y=ysyy' = y \cdot s_y Matrix form: P=SP    [xy1]=[sx000sy0001][xy1]P' = S \cdot P \implies \begin{bmatrix} x' \\ y' \\ 1 \end{bmatrix} = \begin{bmatrix} s_x & 0 & 0 \\ 0 & s_y & 0 \\ 0 & 0 & 1 \end{bmatrix} \begin{bmatrix} x \\ y \\ 1 \end{bmatrix} Note: Scaling is done with respect to the origin. To scale about an arbitrary point (xf,yf)(x_f, y_f), you translate the point to the origin, scale, then translate back.

  3. Rotation: Rotating an object around a point (usually the origin) by an angle θ\theta. If a point P=(x,y)P=(x,y) is rotated counter-clockwise by θ\theta about the origin to P=(x,y)P'=(x',y'), then: x=xcosθysinθx' = x \cos\theta - y \sin\theta y=xsinθ+ycosθy' = x \sin\theta + y \cos\theta Matrix form: P=RP    [xy1]=[cosθsinθ0sinθcosθ0001][xy1]P' = R \cdot P \implies \begin{bmatrix} x' \\ y' \\ 1 \end{bmatrix} = \begin{bmatrix} \cos\theta & -\sin\theta & 0 \\ \sin\theta & \cos\theta & 0 \\ 0 & 0 & 1 \end{bmatrix} \begin{bmatrix} x \\ y \\ 1 \end{bmatrix} Note: To rotate about an arbitrary pivot point (xr,yr)(x_r, y_r), you translate the pivot to the origin, rotate, then translate back.

More Transformations: Reflections and Shears

  1. Reflection: Produces a mirror image of an object.

    • Reflection about the x-axis: x=x,y=yx' = x, y' = -y Rx=[100010001]R_x = \begin{bmatrix} 1 & 0 & 0 \\ 0 & -1 & 0 \\ 0 & 0 & 1 \end{bmatrix}
    • Reflection about the y-axis: x=x,y=yx' = -x, y' = y Ry=[100010001]R_y = \begin{bmatrix} -1 & 0 & 0 \\ 0 & 1 & 0 \\ 0 & 0 & 1 \end{bmatrix}
  2. Shear: Distorts the shape of an object. Lines are slanted.

    • X-shear (relative to x-axis): x=x+shxy,y=yx' = x + sh_x \cdot y, y' = y Shx=[1shx0010001]Sh_x = \begin{bmatrix} 1 & sh_x & 0 \\ 0 & 1 & 0 \\ 0 & 0 & 1 \end{bmatrix}
    • Y-shear (relative to y-axis): x=x,y=y+shyxx' = x, y' = y + sh_y \cdot x Shy=[100shy10001]Sh_y = \begin{bmatrix} 1 & 0 & 0 \\ sh_y & 1 & 0 \\ 0 & 0 & 1 \end{bmatrix}

Homogeneous Coordinates: The Unifying Hero!

You might have noticed the 3×33 \times 3 matrices and points represented as [xy1]\begin{bmatrix} x \\ y \\ 1 \end{bmatrix}. This is homogeneous coordinates. In 2D, we represent a point (x,y)(x,y) as (xh,yh,W)(x_h, y_h, W), where x=xh/Wx = x_h/W and y=yh/Wy = y_h/W. For convenience, we usually use W=1W=1, so (x,y)(x,y) becomes (x,y,1)(x,y,1). Why bother? Homogeneous coordinates allow us to represent all affine transformations (translation, scaling, rotation, shear) as matrix multiplications. Without them, translation would be a matrix addition, making it awkward to combine with other transformations.

Composite Transformations: Power Combos!

Often, we want to apply a sequence of transformations. For example, rotate an object and then move it. This is done by multiplying the transformation matrices. If we apply M1M_1 then M2M_2 to a point PP, the new point PP' is: P=M2(M1P)=(M2M1)P=McompositePP' = M_2 \cdot (M_1 \cdot P) = (M_2 \cdot M_1) \cdot P = M_{composite} \cdot P Crucially, the order of matrix multiplication matters! M1M2M_1 \cdot M_2 is generally NOT the same as M2M1M_2 \cdot M_1. For example, translating then rotating an object gives a different result than rotating then translating it (unless rotating about the origin).


Part 3: Framing Your Masterpiece - 2D Viewing ️

Now that we can draw and transform objects, we need to decide what part of our (potentially infinite) 2D world we want to display and where on the screen it should appear. This is the 2D viewing pipeline.

Window to Viewport Transformation: The Digital Lens

  • World Coordinates: The coordinate system used to define your objects and scene (e.g., meters, feet, or just abstract units).
  • Window (or Clipping Window): A rectangular region in world coordinates that defines what you want to see. It's like the frame of your camera. Defined by (Wxmin,Wymin)(W_{x_{min}}, W_{y_{min}}) and (Wxmax,Wymax)(W_{x_{max}}, W_{y_{max}}).
  • Device Coordinates (or Screen Coordinates): The coordinate system of the display device (e.g., pixels on your screen).
  • Viewport: A rectangular region in device coordinates where the contents of the window will be displayed. Defined by (Vxmin,Vymin)(V_{x_{min}}, V_{y_{min}}) and (Vxmax,Vymax)(V_{x_{max}}, V_{y_{max}}).

The window-to-viewport transformation maps the contents of the window to the viewport. For a point (xw,yw)(xw, yw) in the window, its corresponding point (xv,yv)(xv, yv) in the viewport is: xv=(xwWxmin)VxmaxVxminWxmaxWxmin+Vxminxv = (xw - W_{x_{min}}) \frac{V_{x_{max}} - V_{x_{min}}}{W_{x_{max}} - W_{x_{min}}} + V_{x_{min}} yv=(ywWymin)VymaxVyminWymaxWymin+Vyminyv = (yw - W_{y_{min}}) \frac{V_{y_{max}} - V_{y_{min}}}{W_{y_{max}} - W_{y_{min}}} + V_{y_{min}} This transformation involves scaling and translation to fit the window contents into the viewport, maintaining aspect ratio if desired (which requires a slightly more complex calculation if window and viewport aspect ratios differ).

Clipping: Cutting Out the Excess

Objects or parts of objects that lie outside the window should not be displayed. This process is called clipping.

  1. Line Clipping: Cohen-Sutherland Algorithm A popular algorithm for clipping lines against a rectangular window.

    • Assign a 4-bit region code (outcode) to each endpoint of the line. Each bit corresponds to one of the four boundaries of the window (Top, Bottom, Right, Left - TBRL).
      • Bit 1 (Top): 1 if y>Wymaxy > W_{y_{max}}, else 0
      • Bit 2 (Bottom): 1 if y<Wyminy < W_{y_{min}}, else 0
      • Bit 3 (Right): 1 if x>Wxmaxx > W_{x_{max}}, else 0
      • Bit 4 (Left): 1 if x<Wxminx < W_{x_{min}}, else 0
    • Trivial Accept: If both endpoints have an outcode of 0000 (both are inside), the line is entirely visible.
    • Trivial Reject: If the bitwise AND of the outcodes is not 0000, the line is entirely outside (e.g., both points are to the left of the window).
    • Clipping Required: Otherwise, the line might intersect the window. Calculate intersection points with window boundaries. One endpoint is chosen (usually one that is outside). Its outcode tells which boundary it crosses. Calculate the intersection point (xi,yi)(x_i, y_i) with that boundary. Replace the outside endpoint with the intersection point. Repeat the process.
  2. Polygon Clipping: Sutherland-Hodgman Algorithm Clips a polygon against each edge of a convex clip window, one edge at a time.

    • The algorithm processes the polygon's vertices sequentially against a single clip boundary (e.g., left boundary).
    • For each edge of the polygon (from vertex SS to vertex PP):
      1. If SS and PP are inside: Output PP.
      2. If SS is inside and PP is outside: Output the intersection II of SPSP with the boundary.
      3. If SS and PP are outside: Output nothing.
      4. If SS is outside and PP is inside: Output the intersection II of SPSP with the boundary, then output PP.
    • The output list of vertices from clipping against one boundary becomes the input for the next boundary. After clipping against all four boundaries, the resulting polygon is the clipped polygon.
    • Note: This algorithm works for convex clip windows. For concave clip windows, it can produce disconnected pieces.

A Glimpse into Projections: Adding Depth (Conceptually)

While projections are a cornerstone of 3D graphics, the fundamental idea of mapping from a higher dimension to a lower dimension can be introduced here. In 2D viewing, we are essentially projecting our 2D world coordinates onto a 2D device (the screen).

  • Parallel Projection: Imagine light rays are parallel to each other and perpendicular to the view plane. This preserves the relative size and shape of objects. Orthographic projection is a common type where the view plane is one of the coordinate planes (e.g., viewing the xy-plane). This is implicitly what we do in most 2D graphics.
  • Perspective Projection (Conceptual Link to 3D): In 3D, this is how we achieve realism where objects farther away appear smaller. While not directly applied in the same way for pure 2D-to-2D mapping, understanding that viewing can involve "projection" helps bridge the gap to 3D concepts.

Phew! That Was a 2D Marathon!

We've journeyed through the core of 2D graphics: drawing the basic building blocks, transforming them in various ways, and finally, figuring out how to frame and display them. These concepts are not just theoretical; they are the engine behind countless applications, from simple drawing programs and user interfaces to complex 2D games and animations.

Understanding these fundamentals gives you a powerful lens through which to see and appreciate the digital world around you.