#!/usr/bin/perl # This script can encode and decode between hexadecimal representations of raw # 4-bit images and the peculiar 4-bit RLE format used for keyboard backgrounds # by some MediaTek phones. # It only handles the bitmap itself, not the header or colour table. # # Version 1, made by Dr. Lex, June 2013. See https://www.dr-lex.be/hardware/ # # DECODING: # Paste the bitmap part of the keyboard background into a text file, using hex # codes separated with spaces (e.g. 00 30 91 03 30 ...) # Then, run this script with arguments: -d and the text file. You can write the # result to a file as follows: mtk_rle_coder.pl -d input.txt > output.txt. # # The output will be a sequence of numbers, each character represents one pixel. # You can then edit this in a text editor. You should probably stick to the values # that are already present in the bitmap, although in theory you could use 0 to E. # Ensure the result is exactly as large as the original. # # ENCODING: # Take the edited file (every letter represents one pixel), and run it through # the script, e.g. mtk_rle_coder.pl edited.txt > encoded.txt # The result will again be hex codes separated by spaces. This should not be longer # than the original, shorter is OK. Paste the new hex codes back at the exact # same location where you copied the previous encoded data. Update the data # header: the two bytes after the initial 34 01 must correspond to the total # size of the binary file minus 8, in little-endian format. use strict; use warnings; sub printUsage() { print "Usage: mtk_rle_coder [-d] file\n"; print " -d to decode from hex strings\n"; } sub getPixelCount($); my $bDecode = 0; if( $#ARGV > -1 && $ARGV[0] eq '-d' ) { $bDecode = 1; shift; } my $file = shift; if( ! defined($file) ) { printUsage(); exit(1); } open FILE, "<$file" or die "Cannot open `$file': $!"; my @slurpage = ; close FILE; my $slurpee = join('', @slurpage); $slurpee =~ s/\s//g; # Split into 4-bit values my @hexes = split('', $slurpee); @slurpage = (); $slurpee = ''; my @hexOut; if($bDecode) { my $i = 0; while( $i < $#hexes-1 ) { my $runLength; if($i % 2) { $runLength = hex($hexes[$i+2] . $hexes[$i-1]); } else { $runLength = hex($hexes[$i] . $hexes[$i+1]); } $i += 2; # This is horrible code, it is inefficient, but it works. if( $runLength < 128 ) { my $pixel; if($i % 2) { $pixel = $hexes[$i-1]; } else { $pixel = $hexes[$i+1]; } push(@hexOut, ($pixel) x ($runLength+1)); $i++; } else { $runLength -= 128; if($i % 2) { for( my $j=0; $j<=$runLength; $j++ ) { if( $j % 2 ) { push(@hexOut, $hexes[$i+$j+1]); } else { push(@hexOut, $hexes[$i+$j-1]); } } } else { for( my $j=0; $j<=$runLength; $j++ ) { if( $j % 2 ) { push(@hexOut, $hexes[$i+$j-1]); } else { push(@hexOut, $hexes[$i+$j+1]); } } } $i += $runLength+1; } } print join('', @hexOut) . "\n"; } else { # Encode # This is the most straightforward way of encoding and not the smartest, it is possible # to improve efficiency by avoiding switching between runlength and raw when not profitable. # First, create the raw stream of values my @currentRawPixels; for( my $i=0; $i<=$#hexes; $i++ ) { my $len = 1; while( ($i+$len<=$#hexes) && ($hexes[$i+$len] eq $hexes[$i]) && $len < 128 ) { $len++; } $len--; if( $len == 0 ) { push(@currentRawPixels, $hexes[$i]); if( $#currentRawPixels == 127 ) { push( @hexOut, getPixelCount(127) ); push( @hexOut, @currentRawPixels ); @currentRawPixels = (); } } else { if( $#currentRawPixels > -1 ) { push( @hexOut, getPixelCount($#currentRawPixels) ); push( @hexOut, @currentRawPixels ); @currentRawPixels = (); } my $rLen = sprintf('%02X', $len); push( @hexOut, $rLen); push( @hexOut, $hexes[$i] ); $i += $len; } } if( $#currentRawPixels > -1 ) { push( @hexOut, getPixelCount($#currentRawPixels) ); push( @hexOut, @currentRawPixels ); @currentRawPixels = (); } # Next, put them in the weird semi-little-endian ordering my @hex2; my $cache4Bit = ''; while( $#hexOut >= 0 ) { # Create bytes from the 8-bit and 4-bit values my $hex = shift(@hexOut); if( length($hex) == 2 ) { push( @hex2, $hex ); } else { # Get more data until we're back at an output byte boundary my $nextHex = '0'; $nextHex = shift(@hexOut) if( $#hexOut > -1 ); if( length($nextHex) == 1 ) { push( @hex2, "$nextHex$hex" ); } else { my ($most4,$least4) = split('', $nextHex); # The next value can only be 4-bit my $next4Bit = '0'; $next4Bit = shift(@hexOut) if( $#hexOut > -1 ); # Isn't this lovely and oh so clear? Thank you, Mediatek! push( @hex2, ("$least4$hex", "$next4Bit$most4") ); } } } print join(' ', @hex2) . "\n"; } sub getPixelCount($) { my ($len) = @_; # Prefer run-length notation in case of 1 pixel (as MTK does) if($len == 0) { return sprintf('%02X', $len); } else { return sprintf('%02X', 128+$len); } }