Reading run length compressed BMP images
Table of Contents
Introduction
Quite often, pixels near to each other have the same color, and it is redundant to store the same color information so many times. For instance, lets say 10 pixels in a row have a color x, rather than saving [x][x][x][x][x][x][x][x][x][x], we could just save [10,x] and save a lot of memory. This is the idea behind run length encoding. Although it doesnt achieve the same compression ratio as other compression methods, it is quick to execute and easy to understand. In bmp images, two types of run length encoding can be used, RLE8 and RLE4, used with 8 bits per pixel and 4 bits per pixel images.
When a bmp image is compressed using RLE, it can happen that RLE is used for some pixels and no compression is used for the others. While writing code for compression and decompression, we should keep this in mind. Lets call reading the non compressed pixels absolute mode. The RLE mode is a simple RLE mechanism, the first value contains the count, the second value the pixel to be repeated. If the count value is 0, the second value is special, like EOL (end of line) or delta. In absolute mode, the second value contains the number of value to be copied litteraly. Each absolute run must be word-aligned ( We need to take care to ensure this, else the read values do not make sense) that means you may have to add an aditional padding byte which is not included in the count. After an absolute run, RLE compression continues.
8 bit Run Length Encoding
In 8 bit RLE, each value is a byte. In RLE mode, the first byte contains the count, the second byte the pixel to be repeated. If the count byte is 0, the second byte is special, like EOL (end of line) or delta.
In absolute mode, the second byte contains the number of bytes to be copied litteraly. Each absolute run must be word-aligned ( We need to take care to ensure this, else the read values do not make sense) that means you may have to add an aditional padding byte which is not included in the count. After an absolute run, RLE compression continues.
Second byte | Meaning |
---|---|
0 | End of line |
1 | End of bitmap |
2 | Delta - horizontal and vertical offset |
3-255 | Switch to absolute mode |
def decompress_RLE8
a = Array.new(@height)
@raw_data.sysseek(@offset)
(1..@height).each do |j|
i = 0
row_array = []
len = 0
loop do
first_byte = @raw_data.sysread(1).unpack('C')[0]
second_byte = @raw_data.sysread(1).unpack('C')[0]
if first_byte != 0
(1..first_byte).each do
row_array.push(@palette[second_byte])
len += 1
end
elsif second_byte > 2
sz = (second_byte+1) & (~1)
if second_byte %2 ==1
(1..(sz-1)).each do
len += 1
pixel_val = @raw_data.sysread(1).unpack('C')[0]
row_array.push(@palette[pixel_val])
end
garbage = @raw_data.sysread(1).unpack('C')[0]
else
(1..sz).each do
len += 1
pixel_val = @raw_data.sysread(1).unpack('C')[0]
row_array.push(@palette[pixel_val])
end
end
elsif second_byte == 1
#break
elsif second_byte == 2
puts "Error"
end
break if len >= @width
end
a[@height - j] = row_array
end
puts @raw_data.pos.to_s
return a
end
4 bit Run Length Encoding
RLE4 compression has the same two modes of the RLE_8 compression, absolute and RLE. In the RLE mode, the first byte contains the count of pixels to draw, the second byte contains in its two nibbles (4 bits each) the indices of the pixel colors, the higher 4 bits are the left pixel, the lower 4 bits are the right pixel. Note that two-color runs are possible to encode with RLE_4 through this. Absolute mode is similar to RLE8.
def decompress_RLE4
a = Array.new(height)
(1..@height).each do |j|
i = 0
row_array = []
len = 0
loop do
first_byte = @raw_data.sysread(1).unpack('C')[0]
second_byte = @raw_data.sysread(1).unpack('C')[0]
if first_byte != 0
pixel_two = second_byte & 15
pixel_one = second_byte >> 4
rolling_array = [pixel_one, pixel_two]
k = 0
(1..first_byte).each do
row_array.push(@palette[rolling_array[k]])
k = k ^ 1
len += 1
end
elsif second_byte > 2
sz = (((second_byte + 1)>>1) + 1) & (~1)
if second_byte%2 == 1
temp = 0
(1..sz).each do
len += 2
two_pixels = @raw_data.sysread(1).unpack('C')[0]
pixel_two = two_pixels & 15
pixel_one = two_pixels >> 4
if temp<second_byte
row_array.push(@palette[pixel_one])
temp += 1
end
if temp<second_byte
row_array.push(@palette[pixel_two])
temp += 1
end
end
else
(1..sz).each do
len += 2
two_pixels = @raw_data.sysread(1).unpack('C')[0]
pixel_two = two_pixels & 15
pixel_one = two_pixels >> 4
row_array.push(@palette[pixel_one])
row_array.push(@palette[pixel_two])
end
end
elsif second_byte == 1
break
end
break if len >= @width
end
a[@height - j] = row_array
end
puts @raw_data.pos.to_s
return a
end
Let me know what you think of this article on twitter @harshadixit12!