| 1 | = The PKM picture format - by Karl Maritaud = |
| 2 | |
| 3 | First of all, I'd like to say that I made this file format some years ago when I didn't knew how to load any good format (eg. GIF) and wanted to have my own format. |
| 4 | PKM format was designed to be very simple, easy to encode and decode. Its header is very simple (short) and evolutive. |
| 5 | The only real default I can find in this format is that you can only save 256-color pictures. |
| 6 | I know that you will think: |
| 7 | "Oh no just another fucking format! I'll never use it! Its compression is too poor and I prefer GIF!". |
| 8 | And I'll answer: |
| 9 | "Yeah! You're right. But if you dunno how to load GIF and want a simple format with a quite good compression rate (on simple pictures at least), it could be useful." |
| 10 | |
| 11 | So, here comes the format documentation... |
| 12 | |
| 13 | |
| 14 | = The HEADER: = |
| 15 | |
| 16 | The header is the following 780-byte-structure. (Don't worry about the size. |
| 17 | That's just because the palette is considered as a part of the header). |
| 18 | |
| 19 | || Pos || Field || Type || Size || Description || |
| 20 | || 0 || Signature || char || 3 || Constant string "PKM" (with NO size delimitation '\0' or so...) || |
| 21 | || 3 || Version || byte || 1 || For the moment, it can take only the value 0. Other packing methods may change this field but there is only one for now... || |
| 22 | || 4 || Pack_byte || byte || 1 || Value of the recognition byte for color repetitions that are coded on 1 byte. (See the picture packing section for a better explanation) || |
| 23 | || 5 || Pack_word || byte || 1 || Value of the recognition byte for color repetitions that are coded on 2 bytes. (See the picture packing section...) || |
| 24 | || 6 || Width || word || 2 || Picture width (in pixels) || |
| 25 | || 8 || Height || word || 2 || Picture height (in pixels) || |
| 26 | || 10 || Palette || byte || 768 || RGB palette (RGB RGB ... 256 times) with values from 0 to 63. I know the standard in picture files is 0 to 255 but I find it stupid! It is really easier to send the whole palette in port 3C9h with a REP OUTSB without palette convertion. || |
| 27 | || 778 || PH_size || word || 2 || Post-header size. This is the number of bytes between the header and the picture data. This value can be equal to 0. || |
| 28 | |
| 29 | = The POST-HEADER: = |
| 30 | |
| 31 | The post-header has a variable size. It was designed to support new features |
| 32 | for this file format without changing the whole format. |
| 33 | |
| 34 | It consists in field identifiers followed by their size and their value. |
| 35 | A field identifier is coded with 1 byte and a field size also. |
| 36 | |
| 37 | |
| 38 | == These field identifiers are: == |
| 39 | |
| 40 | (this list may be updated...) |
| 41 | |
| 42 | 0 : Comment on the picture |
| 43 | 1 : Original screen dimensions |
| 44 | 2 : Back color (transparent color) |
| 45 | |
| 46 | If you encounter a field that you don't know just jump over it. But if a |
| 47 | field tells you to jump to a position that is over the beginning of the |
| 48 | picture data, there is an error in the file. |
| 49 | |
| 50 | |
| 51 | == The fields: == |
| 52 | |
| 53 | * Comment: |
| 54 | |
| 55 | With this field, artists will be able to comment their pictures. |
| 56 | Note that GrafX 2 has a comment size limit of 32 chars. But you can |
| 57 | comment a picture with up to 255 chars if you make your own viewer |
| 58 | since GrafX 2 will just ignore extra characters. |
| 59 | |
| 60 | Example: [0],[16],[Picture by X-Man] |
| 61 | This sequence means: |
| 62 | - the field is a comment |
| 63 | - the comment takes 16 characters (there is no end-of-string character |
| 64 | since you know its size) |
| 65 | - the comment is "Picture by X-Man" |
| 66 | |
| 67 | * Original screen dimensions: |
| 68 | |
| 69 | Since GrafX 2 supplies a huge range of resolutions, it seemed convenient |
| 70 | to add a field that indicates what were the original screen dimensions. |
| 71 | |
| 72 | Example: [1],[4],[320],[256] |
| 73 | This sequence means: |
| 74 | - the field is a screen dimensions descriptor |
| 75 | - the dimensions are 2 words (so this value must be always equal to 4) |
| 76 | - the original screen width was 320 pixels |
| 77 | - the original screen height was 256 pixels |
| 78 | |
| 79 | Note that words stored in fields are written Intel-like. The 90% BETA |
| 80 | version did not respect this norm. I'm really sorry about this. This is |
| 81 | not very serious but pictures saved with version 90% and loaded with a |
| 82 | latest version (91% and more) won't set the right resolution. |
| 83 | |
| 84 | * Back color: |
| 85 | |
| 86 | Saving the back color (transparent color) is especially useful when you |
| 87 | want to save a brush. |
| 88 | The size of this field is 1 byte (index of the color between 0 and 255). |
| 89 | |
| 90 | Example: [2],[1],[255] |
| 91 | This sequence means: |
| 92 | - the field is a screen dimensions descriptor |
| 93 | - the value takes 1 byte |
| 94 | - the transparent color is 255 |
| 95 | |
| 96 | |
| 97 | = The PICTURE PACKING METHOD: = |
| 98 | |
| 99 | The PKM compression method is some sort of Run-Length-Compression which is |
| 100 | very efficient on pictures with long horizontal color repetitions. |
| 101 | Actually, the compression is efficient if there are often more than 3 times |
| 102 | the same color consecutively. |
| 103 | |
| 104 | I think that it would be better to give you the algorithm instead of swim- |
| 105 | ming in incomprehensible explanations. |
| 106 | |
| 107 | {{{ |
| 108 | BEGIN |
| 109 | /* |
| 110 | functions: |
| 111 | Read_byte(File) reads and returns 1 byte from File |
| 112 | Draw_pixel(X,Y,Color) draws a pixel of a certain Color at pos. (X,Y) |
| 113 | File_length(File) returns the total length in bytes of File |
| 114 | |
| 115 | variables: |
| 116 | type of Image_size is dword |
| 117 | type of Data_size is dword |
| 118 | type of Packed_data_counter is dword |
| 119 | type of Pixels_counter is dword |
| 120 | type of Color is byte |
| 121 | type of Byte_read is byte |
| 122 | type of Word_read is word |
| 123 | type of Counter is word |
| 124 | type of File is <binary file> |
| 125 | */ |
| 126 | |
| 127 | /* At this point you've already read the header and post-header. */ |
| 128 | |
| 129 | Image_size <- Header.Width * Header.Height |
| 130 | Data_size <- File_length(File) - (780+Header.PH_size) |
| 131 | |
| 132 | Packed_data_counter <- 0 |
| 133 | Pixels_counter <- 0 |
| 134 | |
| 135 | /* Depacking loop: */ |
| 136 | WHILE ((Pixels_counter<Image_size) AND (Packed_data_counter<Data_size)) DO |
| 137 | { |
| 138 | Byte_read <- Read_byte(File) |
| 139 | |
| 140 | /* If it is not a packet recognizer, it's a raw pixel */ |
| 141 | IF ((Byte_read<>Header.Pack_byte) AND (Byte_read<>Header.Pack_word)) |
| 142 | THEN |
| 143 | { |
| 144 | Draw_pixel(Pixels_counter MOD Header.Width, |
| 145 | Pixels_counter DIV Header.Width, |
| 146 | Byte_read) |
| 147 | |
| 148 | Pixels_counter <- Pixels_counter + 1 |
| 149 | Packed_data_counter <- Packed_data_counter + 1 |
| 150 | } |
| 151 | ELSE /* Is the number of pixels to repeat coded... */ |
| 152 | { /* ... with 1 byte */ |
| 153 | IF (Byte_read = Header.Pack_byte) THEN |
| 154 | { |
| 155 | Color <- Read_byte(File) |
| 156 | Byte_read <- Read_byte(File) |
| 157 | |
| 158 | FOR Counter FROM 0 TO (Byte_read-1) STEP +1 |
| 159 | Draw_pixel((Pixels_counter+Counter) MOD Header.Width, |
| 160 | (Pixels_counter+Counter) DIV Header.Width, |
| 161 | Color) |
| 162 | |
| 163 | Pixels_counter <- Pixels_counter + Byte_read |
| 164 | Packed_data_counter <- Packed_data_counter + 3 |
| 165 | } |
| 166 | ELSE /* ... with 2 bytes */ |
| 167 | { |
| 168 | Color <- Read_byte(File) |
| 169 | Word_read <- (word value) (Read_byte(File) SHL 8)+Read_byte(File) |
| 170 | |
| 171 | FOR Counter FROM 0 TO (Word_read-1) STEP +1 |
| 172 | Draw_pixel((Pixels_counter+Counter) MOD Header.Width, |
| 173 | (Pixels_counter+Counter) DIV Header.Width, |
| 174 | Color) |
| 175 | |
| 176 | Pixels_counter <- Pixels_counter + Word_read |
| 177 | Packed_data_counter <- Packed_data_counter + 4 |
| 178 | } |
| 179 | } |
| 180 | } |
| 181 | END |
| 182 | }}} |
| 183 | |
| 184 | For example, the following sequence: |
| 185 | (we suppose that Pack_byte=01 and Pack_word=02) |
| 186 | 04 03 01 05 06 03 02 00 01 2C |
| 187 | will be decoded as: |
| 188 | 04 03 05 05 05 05 05 05 03 00 00 00 ... (repeat 0 300 times (012Ch=300)) |
| 189 | |
| 190 | Repetitions that fit in a word must be written with their higher byte first. |
| 191 | I know that it goes against Intel standard but since I read bytes from the |
| 192 | file thru a buffer (really faster), I don't care about the order (Sorry :)). |
| 193 | But words in the header and post-header must be written and read Intel-like! |
| 194 | |
| 195 | |
| 196 | == Packing advices: == |
| 197 | |
| 198 | * As you can see, there could be a problem when you'd want to pack a raw |
| 199 | pixel with a color equal to Pack_byte or Pack_word. These pixels should |
| 200 | always be coded as a packet even if there is only one pixel. |
| 201 | |
| 202 | Example: (we suppose that Pack_byte=9) |
| 203 | 9 will be encoded 9,9,1 (The 1st 9 in the encoded... |
| 204 | 9,9 will be encoded 9,9,2 ... sequence is Pack_byte) |
| 205 | etc... |
| 206 | |
| 207 | * It seems obvious to find values for Pack_byte and Pack_word that are |
| 208 | (almost) never used. So a small routine that finds the 2 less used colors |
| 209 | in the image should be called before starting to pack the picture. This can |
| 210 | be done almost instantaneously in Assembler. |
| 211 | |
| 212 | * When you want to pack a 2-color-sequence, just write these 2 colors one |
| 213 | after the other (Color,Color) because it only takes 2 bytes instead of 3 if |
| 214 | you had to write a packet (Pack_byte,Color,2). |
| 215 | |
| 216 | * If you pack a very simple picture which has a sequence of more than 65535 |
| 217 | same consecutive bytes, you must break the sequence and continue with a new |
| 218 | packet. |
| 219 | |
| 220 | Example: you have to pack 65635 same consecutive bytes (eg. color 0) |
| 221 | (we suppose that Pack_byte=01 and Pack_word=02) |
| 222 | You'll write: 02 00 FF FF 01 00 64 (FFFFh=65535, 64h=100) |