Assignment Chef icon Assignment Chef
All English tutorials

Programming lesson

Mastering Run-Length Encoding in C++: Build an Image Compression Tool Like a Pro

Learn how to implement run-length encoding (RLE) for image data in C++. This tutorial covers encoding, decoding, hexadecimal conversion, and menu-driven programs—perfect for COP3504c students.

run-length encoding RLE C++ image compression COP3504c project C++ tutorial encode RLE decode RLE hexadecimal conversion data compression pixel art compression C++ programming lossless compression byte manipulation RLE algorithm C++ loops and arrays 2026 programming

Introduction: Why Run-Length Encoding Matters in 2026

In the age of AI-generated art and pixel-perfect game sprites, data compression is more relevant than ever. Run-length encoding (RLE) is a lossless compression technique that powers countless applications, from retro video games like Minecraft and Stardew Valley to medical imaging formats. As a computer science student in 2026, understanding RLE gives you a foundation in how data is stored efficiently—a skill that translates directly to working with large datasets, network protocols, and even machine learning pipelines.

This tutorial walks you through building an RLE image compression tool in C++, mirroring the core tasks of the COP3504c project. You'll learn to encode and decode pixel runs, convert between hexadecimal and decimal representations, and build an interactive menu system—all while practicing loops, strings, arrays, and type-casting.

What Is Run-Length Encoding?

RLE replaces consecutive repeated values (runs) with a count and the value. For example, the flat pixel data [0,0,2,2,2,0,0,0,0,0,0,2,2,0] becomes [2,0,3,2,6,0,2,2,1,0] in RLE: each pair (count, value) represents a run. This is especially effective for images with large uniform areas, like black-and-white pixel art or simple diagrams.

In your project, pixels are represented as bytes (0–15 for 16 colors), and no run may exceed 15 pixels. If a run is longer, you split it into multiple runs—a rule that keeps the encoding simple and byte-aligned.

Setting Up Your C++ Environment

You'll need a C++ compiler (like g++) and a terminal. For this tutorial, we assume you're using a standard library with vector, string, and iostream. The provided console_gfx library handles image display, but our focus is on the core RLE functions.

Core RLE Functions: Step-by-Step Implementation

1. Counting Runs: count_runs

This function returns the number of runs in flat data. For example, [15,15,15,4,4,4,4,4,4] has two runs (three 15s, six 4s).

int count_runs(const vector<byte>& flatData) {&NewLine;    if (flatData.empty()) return 0;&NewLine;    int runs = 1;&NewLine;    int runLength = 1;&NewLine;    for (size_t i = 1; i < flatData.size(); ++i) {&NewLine;        if (flatData[i] == flatData[i-1] && runLength < 15) {&NewLine;            runLength++;&NewLine;        } else {&NewLine;            runs++;&NewLine;            runLength = 1;&NewLine;        }&NewLine;    }&NewLine;    return runs;&NewLine;}

Notice the 15-run limit: if a run hits 15, we start a new run even if the next pixel is the same.

2. Converting to Hexadecimal String: to_hex_string

This utility translates a byte array into a hex string (e.g., [3,15,6,4]"3f64").

string to_hex_string(const vector<byte>& data) {&NewLine;    const char* hexDigits = "0123456789abcdef";&NewLine;    string result;&NewLine;    for (byte b : data) {&NewLine;        result += hexDigits[b];&NewLine;    }&NewLine;    return result;&NewLine;}

3. Encoding Flat Data to RLE: encode_rle

This is the heart of compression. It takes flat pixel data and produces RLE byte pairs.

vector<byte> encode_rle(const vector<byte>& flatData) {&NewLine;    vector<byte> rle;&NewLine;    if (flatData.empty()) return rle;&NewLine;    byte current = flatData[0];&NewLine;    int count = 1;&NewLine;    for (size_t i = 1; i < flatData.size(); ++i) {&NewLine;        if (flatData[i] == current && count < 15) {&NewLine;            count++;&NewLine;        } else {&NewLine;            rle.push_back(static_cast<byte>(count));&NewLine;            rle.push_back(current);&NewLine;            current = flatData[i];&NewLine;            count = 1;&NewLine;        }&NewLine;    }&NewLine;    rle.push_back(static_cast<byte>(count));&NewLine;    rle.push_back(current);&NewLine;    return rle;&NewLine;}

4. Getting Decoded Length: get_decoded_length

Given RLE data, compute the size of the decompressed image.

int get_decoded_length(const vector<byte>& rleData) {&NewLine;    int length = 0;&NewLine;    for (size_t i = 0; i < rleData.size(); i += 2) {&NewLine;        length += rleData[i];&NewLine;    }&NewLine;    return length;&NewLine;}

5. Decoding RLE to Flat Data: decode_rle

The inverse of encoding: expand runs into individual pixels.

vector<byte> decode_rle(const vector<byte>& rleData) {&NewLine;    vector<byte> flat;&NewLine;    for (size_t i = 0; i < rleData.size(); i += 2) {&NewLine;        byte count = rleData[i];&NewLine;        byte value = rleData[i+1];&NewLine;        for (byte j = 0; j < count; ++j) {&NewLine;            flat.push_back(value);&NewLine;        }&NewLine;    }&NewLine;    return flat;&NewLine;}

6. Converting Hex String to Byte Data: string_to_data

Parse a hex string (like "3f64") back into bytes.

vector<byte> string_to_data(const string& dataString) {&NewLine;    vector<byte> data;&NewLine;    for (char c : dataString) {&NewLine;        if (c >= '0' && c <= '9') data.push_back(c - '0');&NewLine;        else if (c >= 'a' && c <= 'f') data.push_back(c - 'a' + 10);&NewLine;        else if (c >= 'A' && c <= 'F') data.push_back(c - 'A' + 10);&NewLine;    }&NewLine;    return data;&NewLine;}

7. Converting RLE to Human-Readable String: to_rle_string

Produce a string like "10f:64" where lengths are decimal and values are hex.

string to_rle_string(const vector<byte>& rleData) {&NewLine;    string result;&NewLine;    for (size_t i = 0; i < rleData.size(); i += 2) {&NewLine;        if (i > 0) result += ':';&NewLine;        result += to_string(rleData[i]);&NewLine;        result += to_hex_string({rleData[i+1]});&NewLine;    }&NewLine;    return result;&NewLine;}

Building the Interactive Menu

Your program should present a menu that lets users load images, input RLE or flat hex data, and display results. Here's a simplified version:

int main() {&NewLine;    cout << "Welcome to RLE Image Processor!" << endl;&NewLine;    vector<byte> currentImage;&NewLine;    int choice;&NewLine;    do {&NewLine;        cout << "1. Load file\n2. Load test image\n3. Read RLE string\n4. Read RLE hex\n5. Read flat hex\n6. Display image\n7. Display RLE string\n8. Display RLE hex\n9. Display flat hex\n0. Exit\n";&NewLine;        cin >> choice;&NewLine;        // handle each option...&NewLine;    } while (choice != 0);&NewLine;    return 0;&NewLine;}

For example, option 3 reads an RLE string like "28:10:6B:10:10B:10:2B:10:12B:10:2B:10:5B:20:11B:10:6B:10" and decodes it into flat data for display.

Putting It All Together: A Real-World Analogy

Think of RLE like a viral TikTok trend: instead of repeating the same dance move 15 times in a video, you just say "do the move 15 times" and then show the move once. That's RLE! In the world of AI image generation, models often output raw pixel data that can be huge; RLE helps shrink that data for faster transmission. For example, the popular AI art tool DALL·E 4 (hypothetical in 2026) might use RLE internally to store intermediate results.

Common Pitfalls and Debugging Tips

  • Off-by-one errors: When counting runs, remember that a run of exactly 15 must be split. Test edge cases like all same pixels.
  • Hex conversion: Ensure you handle both uppercase and lowercase hex digits.
  • Byte vs. int: Use byte (or unsigned char) for pixel values 0–15 to avoid sign extension issues.
  • Memory efficiency: In large images, avoid unnecessary copies; use vector<byte> and pass by reference when possible.

Testing Your Implementation

Use the provided test cases from the project. For instance, encode_rle({15,15,15,4,4,4,4,4,4}) should return {3,15,6,4}. Check that decode_rle inverts encode_rle for random data. Also verify that count_runs matches the number of pairs in RLE data.

Beyond the Assignment: RLE in the Real World

RLE is used in fax machines, BMP image format, and early video game consoles like the NES. In 2026, with the rise of edge computing and IoT, efficient compression is critical for devices with limited memory. Understanding RLE gives you a stepping stone to more advanced techniques like Huffman coding or LZW compression.

Conclusion

You've now built a complete RLE image compression tool in C++. This project reinforces core programming concepts while giving you hands-on experience with a real-world compression algorithm. Whether you're preparing for the COP3504c exam or just love pixel art, mastering RLE is a valuable skill. Now go compress some gator images!