├── .gitignore ├── Makefile ├── sprite.h ├── README.md ├── toaru_jpeg.c └── jpeg.c /.gitignore: -------------------------------------------------------------------------------- 1 | toaru_jpeg 2 | *.o 3 | *.data 4 | *.swp 5 | *.tga 6 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | LDLIBS=-lm 2 | 3 | toaru_jpeg: toaru_jpeg.c jpeg.c 4 | 5 | clean: 6 | rm -f toaru_jpeg 7 | -------------------------------------------------------------------------------- /sprite.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | typedef struct sprite { 6 | uint16_t width; 7 | uint16_t height; 8 | uint32_t * bitmap; 9 | uint32_t * masks; 10 | uint32_t blank; 11 | uint8_t alpha; 12 | } sprite_t; 13 | 14 | #define SPRITE(sprite,x,y) sprite->bitmap[sprite->width * (y) + (x)] 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # toaru\_jpeg 2 | 3 | `toaru_jpeg` is a standalone version of the ToaruOS JPEG decoder, which is itself a rewrite of Raul Aguaviva's Python "micro JPEG visualizer" in C. 4 | 5 | This is a *minimal, incomplete* implementation of the JPEG standard - enough for basic images, but not for any advanced features. Mostly that means non-progressive, 24-bit RGB color images only. 6 | 7 | ## Usage 8 | 9 | This tool produces a TGA image from the input JPG, so you can use it like this to verify it is working for your image: 10 | 11 | make 12 | ./toaru_jpeg ~/path/to/some/simple.jpg 13 | eog out.tga 14 | 15 | ## License 16 | 17 | The Toaru JPEG decoder is itself released under the terms of the [NCSA / University of Illinois License](https://github.com/klange/toaruos/blob/master/LICENSE). 18 | 19 | Raul Aguaviva's Python "micro JPEG visualizer" carried an MIT license. 20 | -------------------------------------------------------------------------------- /toaru_jpeg.c: -------------------------------------------------------------------------------- 1 | /* vim: tabstop=4 shiftwidth=4 noexpandtab 2 | * This file is part of ToaruOS and is released under the terms 3 | * of the NCSA / University of Illinois License - see LICENSE.md 4 | * Copyright (C) 2019 K. Lange 5 | * 6 | * Sprti 7 | */ 8 | 9 | #include 10 | #include "sprite.h" 11 | 12 | #define _RED(color) ((color & 0x00FF0000) / 0x10000) 13 | #define _GRE(color) ((color & 0x0000FF00) / 0x100) 14 | #define _BLU(color) ((color & 0x000000FF) / 0x1) 15 | #define _ALP(color) ((color & 0xFF000000) / 0x1000000) 16 | 17 | extern int load_sprite_jpg(sprite_t * sprite, char * filename); 18 | 19 | sprite_t img = {0}; 20 | 21 | int main(int argc, char * argv[]) { 22 | if (argc < 2) { 23 | fprintf(stdout, "usage: %s IMAGE\n", argv[0]); 24 | return 1; 25 | } 26 | 27 | load_sprite_jpg(&img, argv[1]); 28 | FILE * f = fopen(argv[1], "r"); 29 | 30 | FILE * out = fopen("out.tga","w"); 31 | 32 | struct { 33 | uint8_t id_length; 34 | uint8_t color_map_type; 35 | uint8_t image_type; 36 | 37 | uint16_t color_map_first_entry; 38 | uint16_t color_map_length; 39 | uint8_t color_map_entry_size; 40 | 41 | uint16_t x_origin; 42 | uint16_t y_origin; 43 | uint16_t width; 44 | uint16_t height; 45 | uint8_t depth; 46 | uint8_t descriptor; 47 | } __attribute__((packed)) header = { 48 | 0, /* No image ID field */ 49 | 0, /* No color map */ 50 | 2, /* Uncompressed truecolor */ 51 | 0, 0, 0, /* No color map */ 52 | 0, 0, /* Don't care about origin */ 53 | img.width, img.height, 24, 54 | 0, 55 | }; 56 | 57 | fwrite(&header, 1, sizeof(header), out); 58 | 59 | for (int y = img.height-1; y>=0; y--) { 60 | for (int x = 0; x < img.width; ++x) { 61 | uint8_t buf[3] = { 62 | _BLU(img.bitmap[y * img.width + x]), 63 | _GRE(img.bitmap[y * img.width + x]), 64 | _RED(img.bitmap[y * img.width + x]), 65 | }; 66 | fwrite(buf, 1, 3, out); 67 | } 68 | } 69 | 70 | fclose(out); 71 | 72 | return 0; 73 | 74 | } 75 | -------------------------------------------------------------------------------- /jpeg.c: -------------------------------------------------------------------------------- 1 | /* vim: tabstop=4 shiftwidth=4 noexpandtab 2 | * This file is part of ToaruOS and is released under the terms 3 | * of the NCSA / University of Illinois License - see LICENSE.md 4 | * Copyright (C) 2018 K. Lange 5 | * 6 | * libtoaru_jpeg: Decode simple JPEGs. 7 | * 8 | * Adapted from Raul Aguaviva's Python "micro JPEG visualizer": 9 | * 10 | * MIT License 11 | * 12 | * Copyright (c) 2017 Raul Aguaviva 13 | * 14 | * Permission is hereby granted, free of charge, to any person obtaining a copy 15 | * of this software and associated documentation files (the "Software"), to deal 16 | * in the Software without restriction, including without limitation the rights 17 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 18 | * copies of the Software, and to permit persons to whom the Software is 19 | * furnished to do so, subject to the following conditions: 20 | * 21 | * The above copyright notice and this permission notice shall be included in all 22 | * copies or substantial portions of the Software. 23 | * 24 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 25 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 26 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 27 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 28 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 29 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 30 | * SOFTWARE. 31 | * 32 | */ 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | 39 | #ifdef __toaru__ 40 | #include 41 | #else 42 | #include "sprite.h" 43 | #endif 44 | 45 | #include 46 | #include 47 | 48 | #if 0 49 | #include 50 | #define TRACE_APP_NAME "jpeg" 51 | #else 52 | #define TRACE(...) 53 | #endif 54 | 55 | static sprite_t * sprite = NULL; 56 | static int components = 0; 57 | 58 | /* Byte swap short (because JPEG uses big-endian values) */ 59 | static void swap16(uint16_t * val) { 60 | char * a = (char *)val; 61 | char b = a[0]; 62 | a[0] = a[1]; 63 | a[1] = b; 64 | } 65 | 66 | /* JPEG compontent zig-zag ordering */ 67 | static int zigzag[] = { 68 | 0, 1, 8, 16, 9, 2, 3, 10, 69 | 17, 24, 32, 25, 18, 11, 4, 5, 70 | 12, 19, 26, 33, 40, 48, 41, 34, 71 | 27, 20, 13, 6, 7, 14, 21, 28, 72 | 35, 42, 49, 56, 57, 50, 43, 36, 73 | 29, 22, 15, 23, 30, 37, 44, 51, 74 | 58, 59, 52, 45, 38, 31, 39, 46, 75 | 53, 60, 61, 54, 47, 55, 62, 63 76 | }; 77 | 78 | static uint8_t quant_mapping[3] = {0}; 79 | static uint8_t quant[8][64]; 80 | 81 | static int clamp(int col) { 82 | if (col > 255) return 255; 83 | if (col < 0) return 0; 84 | return col; 85 | } 86 | 87 | /* YCbCr to RGB conversion */ 88 | static void color_conversion( 89 | float Y, float Cb, float Cr, 90 | int *R, int *G, int *B 91 | ) { 92 | float r = (Cr*(2.0-2.0*0.299) + Y); 93 | float b = (Cb*(2.0-2.0*0.114) + Y); 94 | float g = (Y - 0.144 * b - 0.229 * r) / 0.587; 95 | 96 | *R = clamp(r + 128); 97 | *G = clamp(g + 128); 98 | *B = clamp(b + 128); 99 | } 100 | 101 | static int xy_to_lin(int x, int y) { 102 | return x + y * 8; 103 | } 104 | 105 | struct huffman_table { 106 | uint8_t lengths[16]; 107 | uint8_t elements[256]; 108 | } huffman_tables[256] = {0}; 109 | 110 | struct stream { 111 | FILE * file; 112 | uint8_t byte; 113 | int have; 114 | int pos; 115 | }; 116 | 117 | static void define_quant_table(FILE * f, int len) { 118 | 119 | TRACE("Defining quant table"); 120 | while (len > 0) { 121 | uint8_t hdr; 122 | fread(&hdr, 1, 1, f); 123 | fread(&quant[(hdr) & 0xF], 64, 1, f); 124 | len -= 65; 125 | } 126 | TRACE("Done"); 127 | } 128 | 129 | static void baseline_dct(FILE * f, int len) { 130 | 131 | struct dct { 132 | uint8_t hdr; 133 | uint16_t height; 134 | uint16_t width; 135 | uint8_t components; 136 | } __attribute__((packed)) dct; 137 | 138 | fread(&dct, sizeof(struct dct), 1, f); 139 | 140 | /* Read image dimensions, each as big-endian 16-bit values */ 141 | swap16(&dct.height); 142 | swap16(&dct.width); 143 | 144 | /* We read 7 bytes */ 145 | len -= sizeof(struct dct); 146 | 147 | TRACE("Image dimensions are %d×%d", dct.width, dct.height); 148 | sprite->width = dct.width; 149 | sprite->height = dct.height; 150 | sprite->bitmap = malloc(sizeof(uint32_t) * sprite->width * sprite->height); 151 | sprite->masks = NULL; 152 | sprite->alpha = 0; 153 | sprite->blank = 0; 154 | 155 | components = dct.components; 156 | 157 | TRACE("Loading quantization mappings..."); 158 | for (int i = 0; i < dct.components; ++i) { 159 | /* Quant mapping */ 160 | struct { 161 | uint8_t id; 162 | uint8_t samp; 163 | uint8_t qtb_id; 164 | } __attribute__((packed)) tmp; 165 | 166 | fread(&tmp, sizeof(tmp), 1, f); 167 | 168 | /* There should only be three of these for the images we support. */ 169 | if (i > 3) { 170 | abort(); 171 | } 172 | 173 | quant_mapping[i] = tmp.qtb_id; 174 | 175 | /* 3 bytes were read */ 176 | len -= 3; 177 | } 178 | 179 | /* Skip whatever else might be in this section */ 180 | if (len > 0) { 181 | fseek(f, len, SEEK_CUR); 182 | } 183 | } 184 | 185 | static void define_huffman_table(FILE * f, int len) { 186 | 187 | TRACE("Loading Huffman tables..."); 188 | while (len > 0) { 189 | /* Read header ID */ 190 | uint8_t hdr; 191 | fread(&hdr, 1, 1, f); 192 | len--; 193 | 194 | /* Read length table */ 195 | fread(huffman_tables[hdr].lengths, 16, 1, f); 196 | len -= 16; 197 | 198 | /* Read Huffman table entries */ 199 | int o = 0; 200 | for (int i = 0; i < 16; ++i) { 201 | int l = huffman_tables[hdr].lengths[i]; 202 | fread(&huffman_tables[hdr].elements[o], l, 1, f); 203 | o += l; 204 | len -= l; 205 | } 206 | } 207 | 208 | /* Skip rest of section */ 209 | if (len > 0) { 210 | fseek(f, len, SEEK_CUR); 211 | } 212 | } 213 | 214 | struct idct { 215 | float base[64]; 216 | }; 217 | 218 | /** 219 | * norm_coeff[0] = 0.35355339059 220 | * norm_coeff[1] = 0.5 221 | */ 222 | static float cosines[8][8] = { 223 | { 0.35355339059,0.35355339059,0.35355339059,0.35355339059,0.35355339059,0.35355339059,0.35355339059,0.35355339059 }, 224 | { 0.490392640202,0.415734806151,0.27778511651,0.0975451610081,-0.0975451610081,-0.27778511651,-0.415734806151,-0.490392640202 }, 225 | { 0.461939766256,0.191341716183,-0.191341716183,-0.461939766256,-0.461939766256,-0.191341716183,0.191341716183,0.461939766256 }, 226 | { 0.415734806151,-0.0975451610081,-0.490392640202,-0.27778511651,0.27778511651,0.490392640202,0.0975451610081,-0.415734806151 }, 227 | { 0.353553390593,-0.353553390593,-0.353553390593,0.353553390593,0.353553390593,-0.353553390593,-0.353553390593,0.353553390593 }, 228 | { 0.27778511651,-0.490392640202,0.0975451610081,0.415734806151,-0.415734806151,-0.0975451610081,0.490392640202,-0.27778511651 }, 229 | { 0.191341716183,-0.461939766256,0.461939766256,-0.191341716183,-0.191341716183,0.461939766256,-0.461939766256,0.191341716183 }, 230 | { 0.0975451610081,-0.27778511651,0.415734806151,-0.490392640202,0.490392640202,-0.415734806151,0.27778511651,-0.0975451610081 }, 231 | }; 232 | 233 | static float premul[8][8][8][8]= {{{{0}}}}; 234 | 235 | static void add_idc(struct idct * self, int n, int m, int coeff) { 236 | __m128 c = _mm_set_ps(coeff,coeff,coeff,coeff); 237 | for (int y = 0; y < 8; ++y) { 238 | __m128 a, b; 239 | /* base[y][x] = base[y][x] + premul[n][m][y][x] * coeff */ 240 | 241 | /* x = 0..3 */ 242 | a = _mm_load_ps(&premul[n][m][y][0]); 243 | a = _mm_mul_ps(a,c); 244 | b = _mm_load_ps(&self->base[xy_to_lin(0,y)]); 245 | a = _mm_add_ps(a,b); 246 | _mm_store_ps(&self->base[xy_to_lin(0,y)], a); 247 | 248 | /* x = 4..7 */ 249 | a = _mm_load_ps(&premul[n][m][y][4]); 250 | a = _mm_mul_ps(a,c); 251 | b = _mm_load_ps(&self->base[xy_to_lin(4,y)]); 252 | a = _mm_add_ps(a,b); 253 | _mm_store_ps(&self->base[xy_to_lin(4,y)], a); 254 | } 255 | } 256 | 257 | static void add_zigzag(struct idct * self, int zi, int coeff) { 258 | int i = zigzag[zi]; 259 | int n = i & 0x7; 260 | int m = i >> 3; 261 | add_idc(self, n, m, coeff); 262 | } 263 | 264 | /* Read a bit from the stream */ 265 | static int get_bit(struct stream * st) { 266 | while ((st->pos >> 3) >= st->have) { 267 | /* We have finished using the current byte and need to read another one */ 268 | int t = fgetc(st->file); 269 | if (t < 0) { 270 | /* EOF */ 271 | st->byte = 0; 272 | } else { 273 | st->byte = t; 274 | } 275 | 276 | if (st->byte == 0xFF) { 277 | /* 278 | * If we see 0xFF, it's followed by a 0x00 279 | * that should be skipped. 280 | */ 281 | int tmp = fgetc(st->file); 282 | if (tmp != 0) { 283 | /* 284 | * If it's *not*, we reached the end of the file - but 285 | * this shouldn't happen. 286 | */ 287 | st->byte = 0; 288 | } 289 | } 290 | 291 | /* We've seen a new byte */ 292 | st->have++; 293 | } 294 | 295 | /* Extract appropriate bit from this byte */ 296 | uint8_t b = st->byte; 297 | int s = 7 - (st->pos & 0x7); 298 | 299 | /* We move forward one position in the bit stream */ 300 | st->pos += 1; 301 | return (b >> s) & 1; 302 | } 303 | 304 | /* Advance forward and get the n'th next bit */ 305 | static int get_bitn(struct stream * st, int l) { 306 | int val = 0; 307 | for (int i = 0; i < l; ++i) { 308 | val = val * 2 + get_bit(st); 309 | } 310 | return val; 311 | } 312 | 313 | /* 314 | * Read a Huffman code by reading bits and using 315 | * the Huffman table. 316 | */ 317 | static int get_code(struct huffman_table * table, struct stream * st) { 318 | int val = 0; 319 | int off = 0; 320 | int ini = 0; 321 | 322 | for (int i = 0; i < 16; ++i) { 323 | val = val * 2 + get_bit(st); 324 | if (table->lengths[i] > 0) { 325 | if (val - ini < table->lengths[i]) { 326 | return table->elements[off + val - ini]; 327 | } 328 | ini = ini + table->lengths[i]; 329 | off += table->lengths[i]; 330 | } 331 | ini *= 2; 332 | } 333 | 334 | /* Invalid */ 335 | return -1; 336 | } 337 | 338 | /* Decode Huffman codes to values */ 339 | static int decode(int code, int bits) { 340 | int l = 1L << (code - 1); 341 | if (bits >= l) { 342 | return bits; 343 | } else { 344 | return bits - (2 * l - 1); 345 | } 346 | } 347 | 348 | /* Build IDCT matrix */ 349 | static struct idct * build_matrix(struct idct * i, struct stream * st, int idx, uint8_t * quant, int oldcoeff, int * outcoeff) { 350 | memset(i, 0, sizeof(struct idct)); 351 | 352 | int code = get_code(&huffman_tables[idx], st); 353 | int bits = get_bitn(st, code); 354 | int dccoeff = decode(code, bits) + oldcoeff; 355 | 356 | add_zigzag(i, 0, dccoeff * quant[0]); 357 | int l = 1; 358 | 359 | while (l < 64) { 360 | code = get_code(&huffman_tables[16+idx], st); 361 | if (code == 0) break; 362 | if (code > 15) { 363 | l += (code >> 4); 364 | code = code & 0xF; 365 | } 366 | bits = get_bitn(st, code); 367 | int coeff = decode(code, bits); 368 | add_zigzag(i, l, coeff * quant[l]); 369 | l += 1; 370 | } 371 | 372 | *outcoeff = dccoeff; 373 | return i; 374 | } 375 | 376 | /* Set pixel in sprite buffer with bounds checking */ 377 | static void set_pixel(int x, int y, uint32_t color) { 378 | if ((x < sprite->width) && (y < sprite->height)) { 379 | SPRITE(sprite,x,y) = color; 380 | } 381 | } 382 | 383 | /* Concvert YCbCr values to RGB pixels */ 384 | static void draw_matrix(int x, int y, struct idct * L, struct idct * cb, struct idct * cr) { 385 | for (int yy = 0; yy < 8; ++yy) { 386 | for (int xx = 0; xx < 8; ++xx) { 387 | int o = xy_to_lin(xx,yy); 388 | int r, g, b; 389 | color_conversion(L->base[o], cb->base[o], cr->base[o], &r, &g, &b); 390 | uint32_t c = 0xFF000000 | (r << 16) | (g << 8) | b; 391 | set_pixel((x * 8 + xx), (y * 8 + yy), c); 392 | } 393 | } 394 | } 395 | 396 | static void draw_matrix_gray(int x, int y, struct idct * L) { 397 | for (int yy = 0; yy < 8; ++yy) { 398 | for (int xx = 0; xx < 8; ++xx) { 399 | int o = xy_to_lin(xx,yy); 400 | float Y = L->base[o]; 401 | float g = (Y - 0.144 * Y - 0.229 * Y) / 0.587; 402 | uint8_t l = clamp(g + 128); 403 | uint32_t c = 0xFF000000 | (l << 16) | (l << 8) | l; 404 | set_pixel((x * 8 + xx), (y * 8 + yy), c); 405 | } 406 | } 407 | } 408 | 409 | static void start_of_scan(FILE * f, int len) { 410 | 411 | TRACE("Reading image data"); 412 | 413 | /* Skip header */ 414 | fseek(f, len, SEEK_CUR); 415 | 416 | /* Initialize bit stream */ 417 | struct stream _st = {0}; 418 | _st.file = f; 419 | struct stream * st = &_st; 420 | 421 | if (components == 1) { 422 | int old_lum = 0; 423 | for (int y = 0; y < sprite->height / 8 + !!(sprite->height & 0x7); ++y) { 424 | for (int x = 0; x < sprite->width / 8 + !!(sprite->width & 0x7); ++x) { 425 | struct idct matL, matCr, matCb; 426 | build_matrix(&matL, st, 0, quant[quant_mapping[0]], old_lum, &old_lum); 427 | draw_matrix_gray(x,y,&matL); 428 | } 429 | } 430 | } else { 431 | 432 | int old_lum = 0; 433 | int old_crd = 0; 434 | int old_cbd = 0; 435 | for (int y = 0; y < sprite->height / 8 + !!(sprite->height & 0x7); ++y) { 436 | for (int x = 0; x < sprite->width / 8 + !!(sprite->width & 0x7); ++x) { 437 | /* Build matrices */ 438 | struct idct matL, matCr, matCb; 439 | build_matrix(&matL, st, 0, quant[quant_mapping[0]], old_lum, &old_lum); 440 | build_matrix(&matCb, st, 1, quant[quant_mapping[1]], old_cbd, &old_cbd); 441 | build_matrix(&matCr, st, 1, quant[quant_mapping[2]], old_crd, &old_crd); 442 | 443 | draw_matrix(x, y, &matL, &matCb, &matCr); 444 | } 445 | } 446 | } 447 | 448 | TRACE("Done."); 449 | } 450 | 451 | int load_sprite_jpg(sprite_t * tsprite, char * filename) { 452 | FILE * f = fopen(filename, "r"); 453 | if (!f) { 454 | return 1; 455 | } 456 | 457 | sprite = tsprite; 458 | 459 | memset(huffman_tables, 0, sizeof(huffman_tables)); 460 | 461 | if (premul[0][0][0][0] == 0.0) { 462 | for (int n = 0; n < 8; ++n) { 463 | for (int m = 0; m < 8; ++m) { 464 | for (int y = 0; y < 8; ++y) { 465 | for (int x = 0; x < 8; ++x) { 466 | premul[n][m][y][x] = cosines[n][x] * cosines[m][y]; 467 | } 468 | } 469 | } 470 | } 471 | } 472 | 473 | while (1) { 474 | 475 | /* Read a header */ 476 | uint16_t hdr; 477 | int r = fread(&hdr, 2, 1, f); 478 | if (r <= 0) { 479 | /* EOF */ 480 | break; 481 | } 482 | 483 | /* These headers are stored big-endian */ 484 | swap16(&hdr); 485 | 486 | if (hdr == 0xffd8) { 487 | /* No data */ 488 | continue; 489 | } else if (hdr == 0xffd9) { 490 | /* End of file */ 491 | break; 492 | } else { 493 | /* Regular sections with data start with a length */ 494 | uint16_t len; 495 | fread(&len, 2, 1, f); 496 | swap16(&len); 497 | 498 | /* Subtract two because the length includes itself */ 499 | len -= 2; 500 | 501 | if (hdr == 0xffdb) { 502 | define_quant_table(f, len); 503 | } else if (hdr == 0xffc0) { 504 | baseline_dct(f, len); 505 | } else if (hdr == 0xffc4) { 506 | define_huffman_table(f, len); 507 | } else if (hdr == 0xffda) { 508 | start_of_scan(f, len); 509 | /* End immediately after reading the data */ 510 | break; 511 | } else { 512 | TRACE("Unknown header\n"); 513 | fseek(f, len, SEEK_CUR); 514 | } 515 | } 516 | } 517 | 518 | fclose(f); 519 | 520 | return 0; 521 | } 522 | --------------------------------------------------------------------------------