/* * GRUB -- GRand Unified Bootloader * Copyright (C) 2008 Free Software Foundation, Inc. * * GRUB is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * GRUB is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GRUB. If not, see . */ #include #include #include #include #include #include #include GRUB_MOD_LICENSE ("GPLv3+"); /* Uncomment following define to enable JPEG debug. */ //#define JPEG_DEBUG #define JPEG_ESC_CHAR 0xFF #define JPEG_SAMPLING_1x1 0x11 #define JPEG_MARKER_SOI 0xd8 #define JPEG_MARKER_EOI 0xd9 #define JPEG_MARKER_DHT 0xc4 #define JPEG_MARKER_DQT 0xdb #define JPEG_MARKER_SOF0 0xc0 #define JPEG_MARKER_SOS 0xda #define JPEG_MARKER_DRI 0xdd #define JPEG_MARKER_RST0 0xd0 #define JPEG_MARKER_RST1 0xd1 #define JPEG_MARKER_RST2 0xd2 #define JPEG_MARKER_RST3 0xd3 #define JPEG_MARKER_RST4 0xd4 #define JPEG_MARKER_RST5 0xd5 #define JPEG_MARKER_RST6 0xd6 #define JPEG_MARKER_RST7 0xd7 #define SHIFT_BITS 8 #define CONST(x) ((int) ((x) * (1L << SHIFT_BITS) + 0.5)) #define JPEG_UNIT_SIZE 8 static const grub_uint8_t jpeg_zigzag_order[64] = { 0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5, 12, 19, 26, 33, 40, 48, 41, 34, 27, 20, 13, 6, 7, 14, 21, 28, 35, 42, 49, 56, 57, 50, 43, 36, 29, 22, 15, 23, 30, 37, 44, 51, 58, 59, 52, 45, 38, 31, 39, 46, 53, 60, 61, 54, 47, 55, 62, 63 }; #ifdef JPEG_DEBUG static grub_command_t cmd; #endif typedef int jpeg_data_unit_t[64]; struct grub_jpeg_data { grub_file_t file; struct grub_video_bitmap **bitmap; grub_uint8_t *bitmap_ptr; int image_width; int image_height; grub_uint8_t *huff_value[4]; int huff_offset[4][16]; int huff_maxval[4][16]; grub_uint8_t quan_table[2][64]; int comp_index[3][3]; jpeg_data_unit_t ydu[4]; jpeg_data_unit_t crdu; jpeg_data_unit_t cbdu; int vs, hs; int dri; int r1; int dc_value[3]; int bit_mask, bit_save; }; static grub_uint8_t grub_jpeg_get_byte (struct grub_jpeg_data *data) { grub_uint8_t r; r = 0; grub_file_read (data->file, &r, 1); return r; } static grub_uint16_t grub_jpeg_get_word (struct grub_jpeg_data *data) { grub_uint16_t r; r = 0; grub_file_read (data->file, &r, sizeof (grub_uint16_t)); return grub_be_to_cpu16 (r); } static int grub_jpeg_get_bit (struct grub_jpeg_data *data) { int ret; if (data->bit_mask == 0) { data->bit_save = grub_jpeg_get_byte (data); if (data->bit_save == JPEG_ESC_CHAR) { if (grub_jpeg_get_byte (data) != 0) { grub_error (GRUB_ERR_BAD_FILE_TYPE, "jpeg: invalid 0xFF in data stream"); return 0; } } data->bit_mask = 0x80; } ret = ((data->bit_save & data->bit_mask) != 0); data->bit_mask >>= 1; return ret; } static int grub_jpeg_get_number (struct grub_jpeg_data *data, int num) { int value, i, msb; if (num == 0) return 0; msb = value = grub_jpeg_get_bit (data); for (i = 1; i < num; i++) value = (value << 1) + (grub_jpeg_get_bit (data) != 0); if (!msb) value += 1 - (1 << num); return value; } static int grub_jpeg_get_huff_code (struct grub_jpeg_data *data, int id) { int code; unsigned i; code = 0; for (i = 0; i < ARRAY_SIZE (data->huff_maxval[id]); i++) { code <<= 1; if (grub_jpeg_get_bit (data)) code++; if (code < data->huff_maxval[id][i]) return data->huff_value[id][code + data->huff_offset[id][i]]; } grub_error (GRUB_ERR_BAD_FILE_TYPE, "jpeg: huffman decode fails"); return 0; } static grub_err_t grub_jpeg_decode_huff_table (struct grub_jpeg_data *data) { int id, ac, n, base, ofs; grub_uint32_t next_marker; grub_uint8_t count[16]; unsigned i; next_marker = data->file->offset; next_marker += grub_jpeg_get_word (data); while (data->file->offset + sizeof (count) + 1 <= next_marker) { id = grub_jpeg_get_byte (data); ac = (id >> 4) & 1; id &= 0xF; if (id > 1) return grub_error (GRUB_ERR_BAD_FILE_TYPE, "jpeg: too many huffman tables"); if (grub_file_read (data->file, &count, sizeof (count)) != sizeof (count)) return grub_errno; n = 0; for (i = 0; i < ARRAY_SIZE (count); i++) n += count[i]; id += ac * 2; data->huff_value[id] = grub_malloc (n); if (grub_errno) return grub_errno; if (grub_file_read (data->file, data->huff_value[id], n) != n) return grub_errno; base = 0; ofs = 0; for (i = 0; i < ARRAY_SIZE (count); i++) { base += count[i]; ofs += count[i]; data->huff_maxval[id][i] = base; data->huff_offset[id][i] = ofs - base; base <<= 1; } } if (data->file->offset != next_marker) grub_error (GRUB_ERR_BAD_FILE_TYPE, "jpeg: extra byte in huffman table"); return grub_errno; } static grub_err_t grub_jpeg_decode_quan_table (struct grub_jpeg_data *data) { int id; grub_uint32_t next_marker; next_marker = data->file->offset; next_marker += grub_jpeg_get_word (data); while (data->file->offset + sizeof (data->quan_table[id]) + 1 <= next_marker) { id = grub_jpeg_get_byte (data); if (id >= 0x10) /* Upper 4-bit is precision. */ return grub_error (GRUB_ERR_BAD_FILE_TYPE, "jpeg: only 8-bit precision is supported"); if (id > 1) return grub_error (GRUB_ERR_BAD_FILE_TYPE, "jpeg: too many quantization tables"); if (grub_file_read (data->file, &data->quan_table[id], sizeof (data->quan_table[id])) != sizeof (data->quan_table[id])) return grub_errno; } if (data->file->offset != next_marker) grub_error (GRUB_ERR_BAD_FILE_TYPE, "jpeg: extra byte in quantization table"); return grub_errno; } static grub_err_t grub_jpeg_decode_sof (struct grub_jpeg_data *data) { int i, cc; grub_uint32_t next_marker; next_marker = data->file->offset; next_marker += grub_jpeg_get_word (data); if (grub_jpeg_get_byte (data) != 8) return grub_error (GRUB_ERR_BAD_FILE_TYPE, "jpeg: only 8-bit precision is supported"); data->image_height = grub_jpeg_get_word (data); data->image_width = grub_jpeg_get_word (data); if ((!data->image_height) || (!data->image_width)) return grub_error (GRUB_ERR_BAD_FILE_TYPE, "jpeg: invalid image size"); cc = grub_jpeg_get_byte (data); if (cc != 3) return grub_error (GRUB_ERR_BAD_FILE_TYPE, "jpeg: component count must be 3"); for (i = 0; i < cc; i++) { int id, ss; id = grub_jpeg_get_byte (data) - 1; if ((id < 0) || (id >= 3)) return grub_error (GRUB_ERR_BAD_FILE_TYPE, "jpeg: invalid index"); ss = grub_jpeg_get_byte (data); /* Sampling factor. */ if (!id) { data->vs = ss & 0xF; /* Vertical sampling. */ data->hs = ss >> 4; /* Horizontal sampling. */ if ((data->vs > 2) || (data->hs > 2)) return grub_error (GRUB_ERR_BAD_FILE_TYPE, "jpeg: sampling method not supported"); } else if (ss != JPEG_SAMPLING_1x1) return grub_error (GRUB_ERR_BAD_FILE_TYPE, "jpeg: sampling method not supported"); data->comp_index[id][0] = grub_jpeg_get_byte (data); } if (data->file->offset != next_marker) grub_error (GRUB_ERR_BAD_FILE_TYPE, "jpeg: extra byte in sof"); return grub_errno; } static grub_err_t grub_jpeg_decode_dri (struct grub_jpeg_data *data) { if (grub_jpeg_get_word (data) != 4) return grub_error (GRUB_ERR_BAD_FILE_TYPE, "jpeg: DRI marker length must be 4"); data->dri = grub_jpeg_get_word (data); return grub_errno; } static void grub_jpeg_idct_transform (jpeg_data_unit_t du) { int *pd; int i; int t0, t1, t2, t3, t4, t5, t6, t7; int v0, v1, v2, v3, v4; pd = du; for (i = 0; i < JPEG_UNIT_SIZE; i++, pd++) { if ((pd[JPEG_UNIT_SIZE * 1] | pd[JPEG_UNIT_SIZE * 2] | pd[JPEG_UNIT_SIZE * 3] | pd[JPEG_UNIT_SIZE * 4] | pd[JPEG_UNIT_SIZE * 5] | pd[JPEG_UNIT_SIZE * 6] | pd[JPEG_UNIT_SIZE * 7]) == 0) { pd[JPEG_UNIT_SIZE * 0] <<= SHIFT_BITS; pd[JPEG_UNIT_SIZE * 1] = pd[JPEG_UNIT_SIZE * 2] = pd[JPEG_UNIT_SIZE * 3] = pd[JPEG_UNIT_SIZE * 4] = pd[JPEG_UNIT_SIZE * 5] = pd[JPEG_UNIT_SIZE * 6] = pd[JPEG_UNIT_SIZE * 7] = pd[JPEG_UNIT_SIZE * 0]; continue; } t0 = pd[JPEG_UNIT_SIZE * 0]; t1 = pd[JPEG_UNIT_SIZE * 2]; t2 = pd[JPEG_UNIT_SIZE * 4]; t3 = pd[JPEG_UNIT_SIZE * 6]; v4 = (t1 + t3) * CONST (0.541196100); v0 = ((t0 + t2) << SHIFT_BITS); v1 = ((t0 - t2) << SHIFT_BITS); v2 = v4 - t3 * CONST (1.847759065); v3 = v4 + t1 * CONST (0.765366865); t0 = v0 + v3; t3 = v0 - v3; t1 = v1 + v2; t2 = v1 - v2; t4 = pd[JPEG_UNIT_SIZE * 7]; t5 = pd[JPEG_UNIT_SIZE * 5]; t6 = pd[JPEG_UNIT_SIZE * 3]; t7 = pd[JPEG_UNIT_SIZE * 1]; v0 = t4 + t7; v1 = t5 + t6; v2 = t4 + t6; v3 = t5 + t7; v4 = (v2 + v3) * CONST (1.175875602); v0 *= CONST (0.899976223); v1 *= CONST (2.562915447); v2 = v2 * CONST (1.961570560) - v4; v3 = v3 * CONST (0.390180644) - v4; t4 = t4 * CONST (0.298631336) - v0 - v2; t5 = t5 * CONST (2.053119869) - v1 - v3; t6 = t6 * CONST (3.072711026) - v1 - v2; t7 = t7 * CONST (1.501321110) - v0 - v3; pd[JPEG_UNIT_SIZE * 0] = t0 + t7; pd[JPEG_UNIT_SIZE * 7] = t0 - t7; pd[JPEG_UNIT_SIZE * 1] = t1 + t6; pd[JPEG_UNIT_SIZE * 6] = t1 - t6; pd[JPEG_UNIT_SIZE * 2] = t2 + t5; pd[JPEG_UNIT_SIZE * 5] = t2 - t5; pd[JPEG_UNIT_SIZE * 3] = t3 + t4; pd[JPEG_UNIT_SIZE * 4] = t3 - t4; } pd = du; for (i = 0; i < JPEG_UNIT_SIZE; i++, pd += JPEG_UNIT_SIZE) { if ((pd[1] | pd[2] | pd[3] | pd[4] | pd[5] | pd[6] | pd[7]) == 0) { pd[0] >>= (SHIFT_BITS + 3); pd[1] = pd[2] = pd[3] = pd[4] = pd[5] = pd[6] = pd[7] = pd[0]; continue; } v4 = (pd[2] + pd[6]) * CONST (0.541196100); v0 = (pd[0] + pd[4]) << SHIFT_BITS; v1 = (pd[0] - pd[4]) << SHIFT_BITS; v2 = v4 - pd[6] * CONST (1.847759065); v3 = v4 + pd[2] * CONST (0.765366865); t0 = v0 + v3; t3 = v0 - v3; t1 = v1 + v2; t2 = v1 - v2; t4 = pd[7]; t5 = pd[5]; t6 = pd[3]; t7 = pd[1]; v0 = t4 + t7; v1 = t5 + t6; v2 = t4 + t6; v3 = t5 + t7; v4 = (v2 + v3) * CONST (1.175875602); v0 *= CONST (0.899976223); v1 *= CONST (2.562915447); v2 = v2 * CONST (1.961570560) - v4; v3 = v3 * CONST (0.390180644) - v4; t4 = t4 * CONST (0.298631336) - v0 - v2; t5 = t5 * CONST (2.053119869) - v1 - v3; t6 = t6 * CONST (3.072711026) - v1 - v2; t7 = t7 * CONST (1.501321110) - v0 - v3; pd[0] = (t0 + t7) >> (SHIFT_BITS * 2 + 3); pd[7] = (t0 - t7) >> (SHIFT_BITS * 2 + 3); pd[1] = (t1 + t6) >> (SHIFT_BITS * 2 + 3); pd[6] = (t1 - t6) >> (SHIFT_BITS * 2 + 3); pd[2] = (t2 + t5) >> (SHIFT_BITS * 2 + 3); pd[5] = (t2 - t5) >> (SHIFT_BITS * 2 + 3); pd[3] = (t3 + t4) >> (SHIFT_BITS * 2 + 3); pd[4] = (t3 - t4) >> (SHIFT_BITS * 2 + 3); } for (i = 0; i < JPEG_UNIT_SIZE * JPEG_UNIT_SIZE; i++) { du[i] += 128; if (du[i] < 0) du[i] = 0; if (du[i] > 255) du[i] = 255; } } static void grub_jpeg_decode_du (struct grub_jpeg_data *data, int id, jpeg_data_unit_t du) { int h1, h2, qt; unsigned pos; grub_memset (du, 0, sizeof (jpeg_data_unit_t)); qt = data->comp_index[id][0]; h1 = data->comp_index[id][1]; h2 = data->comp_index[id][2]; data->dc_value[id] += grub_jpeg_get_number (data, grub_jpeg_get_huff_code (data, h1)); du[0] = data->dc_value[id] * (int) data->quan_table[qt][0]; pos = 1; while (pos < ARRAY_SIZE (data->quan_table[qt])) { int num, val; num = grub_jpeg_get_huff_code (data, h2); if (!num) break; val = grub_jpeg_get_number (data, num & 0xF); num >>= 4; pos += num; du[jpeg_zigzag_order[pos]] = val * (int) data->quan_table[qt][pos]; pos++; } grub_jpeg_idct_transform (du); } static void grub_jpeg_ycrcb_to_rgb (int yy, int cr, int cb, grub_uint8_t * rgb) { int dd; cr -= 128; cb -= 128; /* Red */ dd = yy + ((cr * CONST (1.402)) >> SHIFT_BITS); if (dd < 0) dd = 0; if (dd > 255) dd = 255; #ifdef GRUB_CPU_WORDS_BIGENDIAN rgb[2] = dd; #else *(rgb++) = dd; #endif /* Green */ dd = yy - ((cb * CONST (0.34414) + cr * CONST (0.71414)) >> SHIFT_BITS); if (dd < 0) dd = 0; if (dd > 255) dd = 255; #ifdef GRUB_CPU_WORDS_BIGENDIAN rgb[1] = dd; #else *(rgb++) = dd; #endif /* Blue */ dd = yy + ((cb * CONST (1.772)) >> SHIFT_BITS); if (dd < 0) dd = 0; if (dd > 255) dd = 255; #ifdef GRUB_CPU_WORDS_BIGENDIAN rgb[0] = dd; rgb += 3; #else *(rgb++) = dd; #endif } static grub_err_t grub_jpeg_decode_sos (struct grub_jpeg_data *data) { int i, cc; grub_uint32_t data_offset; data_offset = data->file->offset; data_offset += grub_jpeg_get_word (data); cc = grub_jpeg_get_byte (data); if (cc != 3) return grub_error (GRUB_ERR_BAD_FILE_TYPE, "jpeg: component count must be 3"); for (i = 0; i < cc; i++) { int id, ht; id = grub_jpeg_get_byte (data) - 1; if ((id < 0) || (id >= 3)) return grub_error (GRUB_ERR_BAD_FILE_TYPE, "jpeg: invalid index"); ht = grub_jpeg_get_byte (data); data->comp_index[id][1] = (ht >> 4); data->comp_index[id][2] = (ht & 0xF) + 2; } grub_jpeg_get_byte (data); /* Skip 3 unused bytes. */ grub_jpeg_get_word (data); if (data->file->offset != data_offset) return grub_error (GRUB_ERR_BAD_FILE_TYPE, "jpeg: extra byte in sos"); if (grub_video_bitmap_create (data->bitmap, data->image_width, data->image_height, GRUB_VIDEO_BLIT_FORMAT_RGB_888)) return grub_errno; data->bitmap_ptr = (*data->bitmap)->data; return GRUB_ERR_NONE; } static grub_err_t grub_jpeg_decode_data (struct grub_jpeg_data *data) { int c1, vb, hb, nr1, nc1; int rst = data->dri; vb = data->vs * 8; hb = data->hs * 8; nr1 = (data->image_height + vb - 1) / vb; nc1 = (data->image_width + hb - 1) / hb; for (; data->r1 < nr1 && (!data->dri || rst); data->r1++, data->bitmap_ptr += (vb * data->image_width - hb * nc1) * 3) for (c1 = 0; c1 < nc1 && (!data->dri || rst); c1++, rst--, data->bitmap_ptr += hb * 3) { int r2, c2, nr2, nc2; grub_uint8_t *ptr2; for (r2 = 0; r2 < data->vs; r2++) for (c2 = 0; c2 < data->hs; c2++) grub_jpeg_decode_du (data, 0, data->ydu[r2 * 2 + c2]); grub_jpeg_decode_du (data, 1, data->cbdu); grub_jpeg_decode_du (data, 2, data->crdu); if (grub_errno) return grub_errno; nr2 = (data->r1 == nr1 - 1) ? (data->image_height - data->r1 * vb) : vb; nc2 = (c1 == nc1 - 1) ? (data->image_width - c1 * hb) : hb; ptr2 = data->bitmap_ptr; for (r2 = 0; r2 < nr2; r2++, ptr2 += (data->image_width - nc2) * 3) for (c2 = 0; c2 < nc2; c2++, ptr2 += 3) { int i0, yy, cr, cb; i0 = (r2 / data->vs) * 8 + (c2 / data->hs); cr = data->crdu[i0]; cb = data->cbdu[i0]; yy = data->ydu[(r2 / 8) * 2 + (c2 / 8)][(r2 % 8) * 8 + (c2 % 8)]; grub_jpeg_ycrcb_to_rgb (yy, cr, cb, ptr2); } } return grub_errno; } static void grub_jpeg_reset (struct grub_jpeg_data *data) { data->bit_mask = 0x0; data->dc_value[0] = 0; data->dc_value[1] = 0; data->dc_value[2] = 0; } static grub_uint8_t grub_jpeg_get_marker (struct grub_jpeg_data *data) { grub_uint8_t r; r = grub_jpeg_get_byte (data); if (r != JPEG_ESC_CHAR) { grub_error (GRUB_ERR_BAD_FILE_TYPE, "jpeg: invalid maker"); return 0; } return grub_jpeg_get_byte (data); } static grub_err_t grub_jpeg_decode_jpeg (struct grub_jpeg_data *data) { if (grub_jpeg_get_marker (data) != JPEG_MARKER_SOI) /* Start Of Image. */ return grub_error (GRUB_ERR_BAD_FILE_TYPE, "jpeg: invalid jpeg file"); while (grub_errno == 0) { grub_uint8_t marker; marker = grub_jpeg_get_marker (data); if (grub_errno) break; grub_dprintf ("jpeg", "jpeg marker: %x\n", marker); switch (marker) { case JPEG_MARKER_DHT: /* Define Huffman Table. */ grub_jpeg_decode_huff_table (data); break; case JPEG_MARKER_DQT: /* Define Quantization Table. */ grub_jpeg_decode_quan_table (data); break; case JPEG_MARKER_SOF0: /* Start Of Frame 0. */ grub_jpeg_decode_sof (data); break; case JPEG_MARKER_DRI: /* Define Restart Interval. */ grub_jpeg_decode_dri (data); break; case JPEG_MARKER_SOS: /* Start Of Scan. */ if (grub_jpeg_decode_sos (data)) break; case JPEG_MARKER_RST0: /* Restart. */ case JPEG_MARKER_RST1: case JPEG_MARKER_RST2: case JPEG_MARKER_RST3: case JPEG_MARKER_RST4: case JPEG_MARKER_RST5: case JPEG_MARKER_RST6: case JPEG_MARKER_RST7: grub_jpeg_decode_data (data); grub_jpeg_reset (data); break; case JPEG_MARKER_EOI: /* End Of Image. */ return grub_errno; default: /* Skip unrecognized marker. */ { grub_uint16_t sz; sz = grub_jpeg_get_word (data); if (grub_errno) return (grub_errno); grub_file_seek (data->file, data->file->offset + sz - 2); } } } return grub_errno; } static grub_err_t grub_video_reader_jpeg (struct grub_video_bitmap **bitmap, const char *filename) { grub_file_t file; struct grub_jpeg_data *data; file = grub_buffile_open (filename, 0); if (!file) return grub_errno; data = grub_zalloc (sizeof (*data)); if (data != NULL) { int i; data->file = file; data->bitmap = bitmap; grub_jpeg_decode_jpeg (data); for (i = 0; i < 4; i++) grub_free (data->huff_value[i]); grub_free (data); } if (grub_errno != GRUB_ERR_NONE) { grub_video_bitmap_destroy (*bitmap); *bitmap = 0; } grub_file_close (file); return grub_errno; } #if defined(JPEG_DEBUG) static grub_err_t grub_cmd_jpegtest (grub_command_t cmd __attribute__ ((unused)), int argc, char **args) { struct grub_video_bitmap *bitmap = 0; if (argc != 1) return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("filename expected")); grub_video_reader_jpeg (&bitmap, args[0]); if (grub_errno != GRUB_ERR_NONE) return grub_errno; grub_video_bitmap_destroy (bitmap); return GRUB_ERR_NONE; } #endif static struct grub_video_bitmap_reader jpg_reader = { .extension = ".jpg", .reader = grub_video_reader_jpeg, .next = 0 }; static struct grub_video_bitmap_reader jpeg_reader = { .extension = ".jpeg", .reader = grub_video_reader_jpeg, .next = 0 }; GRUB_MOD_INIT (jpeg) { grub_video_bitmap_reader_register (&jpg_reader); grub_video_bitmap_reader_register (&jpeg_reader); #if defined(JPEG_DEBUG) cmd = grub_register_command ("jpegtest", grub_cmd_jpegtest, "FILE", "Tests loading of JPEG bitmap."); #endif } GRUB_MOD_FINI (jpeg) { #if defined(JPEG_DEBUG) grub_unregister_command (cmd); #endif grub_video_bitmap_reader_unregister (&jpeg_reader); grub_video_bitmap_reader_unregister (&jpg_reader); }