jpeg: decode RGB images

Both decoders treated every 3-component image as YCbCr, so RGB JPEGs
(as written by cjpeg -rgb, and by some Adobe software) came out with
wrong colours.

Decide the colour space as libjpeg does: a JFIF marker means YCbCr;
otherwise the transform flag of an Adobe APP14 marker decides (0 is
RGB); otherwise component IDs 'R', 'G', 'B' mean RGB.

Core loader: on colour targets R, G and B are stored in place in the
row buffer and the YUV conversion is skipped. Greyscale builds now
also decode G and B for RGB and combine them into luma per block,
which needs every component to be one block per MCU; other RGB
layouts are rejected there.

Plugin: RGB needs one block per MCU for every component, otherwise it
is rejected (colour targets fall back to jpegp). Colour builds convert
the R, G and B planes to YCbCr in place after decoding, so display and
greyscale view modes are unchanged; greyscale builds combine R, G and
B into luma per block as the core does.

Code size on the e200: core loader +351 bytes, plugin decoder +603
bytes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Change-Id: Ib24a0b7690ca4c00b3ac01b2f511309efeac5975
This commit is contained in:
Michael Giacomelli 2026-09-29 11:55:36 -04:00 • committed by Solomon Peachy
parent 5dc5c4a06c
commit 142a6fbb39
4 changed files with 184 additions and 40 deletions

View file

@ -674,6 +674,16 @@ int process_markers(unsigned char* p_src, long size, struct jpeg* p_jpeg)
return (-5); /* Huffman table index out of range */
}
}
p_jpeg->rgb = n == 3 && jpeg_is_rgb(p_jpeg->jfif, p_jpeg->adobe,
p_jpeg->frameheader[0].ID, p_jpeg->frameheader[1].ID,
p_jpeg->frameheader[2].ID);
/* RGB needs every component in one block per MCU: the
planes are converted to YCbCr in place (colour) or
combined per block (greyscale) */
if (p_jpeg->rgb
&& (p_jpeg->frameheader[0].horizontal_sampling != 1
|| p_jpeg->frameheader[0].vertical_sampling != 1))
return -3; /* Unsupported SOF0 subsampling */
p_src += 3; /* skip spectral information */
p_jpeg->p_entropy_data = p_src;
p_end = p_src; /* exit while loop */
@ -713,7 +723,6 @@ int process_markers(unsigned char* p_src, long size, struct jpeg* p_jpeg)
case 0xDC: /* Define Number of Lines */
case 0xDE: /* Define Hierarchical progression */
case 0xDF: /* Expand Reference Component(s) */
case 0xE0: /* Application Field 0*/
case 0xE1: /* Application Field 1*/
case 0xE2: /* Application Field 2*/
case 0xE3: /* Application Field 3*/
@ -727,7 +736,6 @@ int process_markers(unsigned char* p_src, long size, struct jpeg* p_jpeg)
case 0xEB: /* Application Field 11*/
case 0xEC: /* Application Field 12*/
case 0xED: /* Application Field 13*/
case 0xEE: /* Application Field 14*/
case 0xEF: /* Application Field 15*/
case 0xFE: /* Comment */
{
@ -737,6 +745,19 @@ int process_markers(unsigned char* p_src, long size, struct jpeg* p_jpeg)
}
break;
case 0xE0: /* Application Field 0 (JFIF) */
case 0xEE: /* Application Field 14 (Adobe) */
{
int marker = p_src[-1];
marker_size = *p_src++ << 8; /* Highbyte */
marker_size |= *p_src++; /* Lowbyte */
n = MIN(marker_size - 2, p_end - p_src);
jpeg_app_colorspace(marker, p_src, MIN(n, 12), &p_jpeg->jfif,
&p_jpeg->adobe);
p_src += marker_size-2; /* skip segment */
}
break;
case 0xF0: /* Reserved for JPEG extensions */
case 0xF1: /* Reserved for JPEG extensions */
case 0xF2: /* Reserved for JPEG extensions */
@ -1387,6 +1408,20 @@ int jpeg_decode(struct jpeg* p_jpeg, unsigned char* p_pixel[3],
pf_progress(y, p_jpeg->y_mbl-1); /* notify about decoding progress */
} /* for y */
if (p_jpeg->rgb)
{ /* the display expects YCbCr: convert the equal sized R, G and B
planes in place (JFIF equations) */
unsigned char *pr = p_pixel[0], *pg = p_pixel[1], *pb = p_pixel[2];
unsigned char *end = pr + width * height;
for (; pr < end; pr++, pg++, pb++)
{
int r = *pr, g = *pg, b = *pb;
*pr = (77 * r + 150 * g + 29 * b + 128) >> 8;
*pg = clamp_component((-43 * r - 85 * g + 128 * b + 32896) >> 8);
*pb = clamp_component((128 * r - 107 * g - 21 * b + 32896) >> 8);
}
}
return 0; /* success */
}
#else /* !HAVE_LCD_COLOR */
@ -1411,7 +1446,8 @@ int jpeg_decode(struct jpeg* p_jpeg, unsigned char* p_pixel[1], int downscale,
int k_need; /* AC coefficients needed up to here */
int zero_need; /* init the block with this many zeros */
int last_dc_val = 0;
int last_dc_val[3] = {0, 0, 0};
unsigned char rgb_tmp[2][64]; /* R and G of an RGB MCU */
int store_offs[4]; /* memory offsets: order of Y11 Y12 Y21 Y22 U V */
int restart = p_jpeg->restart_interval; /* MCUs until restart marker */
@ -1482,10 +1518,10 @@ int jpeg_decode(struct jpeg* p_jpeg, unsigned char* p_pixel[1], int downscale,
/* Section F.2.2.1: decode the DC coefficient difference */
s = huff_decode_dc(&bs, dctbl);
if (ci == 0) /* only for Y component */
if (ci == 0 || p_jpeg->rgb) /* Y, or all of R, G, B */
{
last_dc_val += s;
block[0] = last_dc_val; /* output it (assumes zag[0] = 0) */
last_dc_val[ci] += s;
block[0] = last_dc_val[ci]; /* output it (zag[0] = 0) */
/* coefficient buffer must be cleared */
MEMSET(block+1, 0, zero_need*sizeof(block[0]));
@ -1536,18 +1572,35 @@ int jpeg_decode(struct jpeg* p_jpeg, unsigned char* p_pixel[1], int downscale,
}
} /* for k */
if (ci == 0)
if (ci == 0 && !p_jpeg->rgb)
{ /* only for Y component */
pf_idct(p_byte+store_offs[blkn], block, p_jpeg->qt_idct[ci],
skip_line);
}
else if (p_jpeg->rgb && ci < 2)
{ /* keep R and G until B is decoded */
pf_idct(rgb_tmp[ci], block, p_jpeg->qt_idct[ci], 8);
}
else if (p_jpeg->rgb)
{ /* luma from R, G and B (JFIF weights) */
int n = 8 / downscale;
int xi, yi;
unsigned char *p = p_byte;
pf_idct(p, block, p_jpeg->qt_idct[ci], skip_line);
for (yi = 0; yi < n; yi++, p += skip_line)
for (xi = 0; xi < n; xi++)
p[xi] = (77 * rgb_tmp[0][yi * 8 + xi]
+ 150 * rgb_tmp[1][yi * 8 + xi]
+ 29 * p[xi] + 128) >> 8;
}
} /* for blkn */
p_byte += skip_mcu;
if (p_jpeg->restart_interval && --restart == 0)
{ /* if a restart marker is due: */
restart = p_jpeg->restart_interval; /* count again */
search_restart(&bs); /* align the bitstream */
last_dc_val = 0; /* reset decoder */
last_dc_val[0] = last_dc_val[1] =
last_dc_val[2] = 0; /* reset decoder */
}
} /* for x */
if (pf_progress != NULL)

View file

@ -37,6 +37,9 @@ struct jpeg
int blocks; /* blocks per MB */
int restart_interval; /* number of MCUs between RSTm markers */
int store_pos[4]; /* for Y block ordering */
bool jfif; /* saw a JFIF APP0 marker */
unsigned char adobe; /* Adobe APP14 transform flag + 1, 0 if none */
bool rgb; /* the components are R, G, B rather than Y, Cb, Cr */
unsigned char* p_entropy_data;
unsigned char* p_entropy_end;

View file

@ -27,6 +27,7 @@
#ifndef _JPEG_COMMON_H
#define _JPEG_COMMON_H
#include <stdbool.h>
#include "bmp.h"
#define HUFF_LOOKAHEAD 8 /* # of bits of lookahead */
@ -80,6 +81,40 @@ union uint8_rgbyuv {
struct uint8_rgb rgb;
};
/* Record what an APP0 (JFIF) or APP14 (Adobe) segment says about the
* colour space, given its first n bytes (at most 12 are needed). *adobe gets
* the Adobe transform flag + 1. */
static inline void jpeg_app_colorspace(int marker, const unsigned char *id,
int n, bool *jfif, unsigned char *adobe)
{
bool is_jfif = marker == 0xE0;
const char *sig = is_jfif ? "JFIF" : "Adobe"; /* 5 bytes with the NUL */
int i;
if (n < (is_jfif ? 5 : 12))
return;
for (i = 0; i < 5; i++)
if (id[i] != (unsigned char)sig[i])
return;
if (is_jfif)
*jfif = true;
else
*adobe = id[11] + 1;
}
/* Whether a 3-component image holds RGB rather than YCbCr, decided as
* libjpeg does: a JFIF marker means YCbCr; otherwise an Adobe marker's
* transform flag decides (0 is RGB); otherwise component IDs 'R', 'G', 'B'
* mean RGB. adobe is the transform flag + 1, or 0 without an Adobe marker. */
static inline bool jpeg_is_rgb(bool jfif, int adobe, int id0, int id1, int id2)
{
if (jfif)
return false;
if (adobe)
return adobe == 1;
return id0 == 'R' && id1 == 'G' && id2 == 'B';
}
static inline int clamp_component(int x)
{
if(x > 255) return 255;

View file

@ -56,6 +56,13 @@ typedef uint8_t jpeg_pix_t;
#endif
#define JPEG_IDCT_TRANSPOSE
#define JPEG_PIX_SZ (sizeof(jpeg_pix_t))
/* index into the per-scale arrays: luma or chroma in colour builds; a
greyscale build only decodes chroma for RGB, which is at the luma scale */
#ifdef HAVE_LCD_COLOR
#define SCALE_IDX(ci) (!!(ci))
#else
#define SCALE_IDX(ci) 0
#endif
#ifdef HAVE_LCD_COLOR
#define COLOR_EXTRA_IDCT_WS 64
#else
@ -106,14 +113,16 @@ struct jpeg
int cur_row; /* current row relative to top of image */
int set_rows;
int store_pos[4]; /* for Y block ordering */
#ifdef HAVE_LCD_COLOR
bool jfif; /* saw a JFIF APP0 marker */
unsigned char adobe; /* Adobe APP14 transform flag + 1, 0 if none */
bool rgb; /* the components are R, G, B rather than Y, Cb, Cr */
int last_dc_val[3];
#ifdef HAVE_LCD_COLOR
int h_scale[2]; /* horizontal scalefactor = (2**N) / 8 */
int v_scale[2]; /* same as above, for vertical direction */
int k_need[2]; /* per component zig-zag index of last needed coefficient */
int zero_need[2]; /* per compenent number of coefficients to zero */
#else
int last_dc_val;
int h_scale[1]; /* horizontal scalefactor = (2**N) / 8 */
int v_scale[1]; /* same as above, for vertical direction */
int k_need[1]; /* per component zig-zag index of last needed coefficient */
@ -1198,6 +1207,17 @@ static int process_markers(struct jpeg* p_jpeg)
return (-5); /* Huffman table index out of range */
}
}
p_jpeg->rgb = n == 3 && jpeg_is_rgb(p_jpeg->jfif, p_jpeg->adobe,
p_jpeg->frameheader[0].ID, p_jpeg->frameheader[1].ID,
p_jpeg->frameheader[2].ID);
#ifndef HAVE_LCD_COLOR
/* greyscale RGB is combined per block, so all components
must be one block per MCU (chroma is then 1x1 too) */
if (p_jpeg->rgb
&& (p_jpeg->frameheader[0].horizontal_sampling != 1
|| p_jpeg->frameheader[0].vertical_sampling != 1))
return -3; /* Unsupported SOF0 subsampling */
#endif
/* skip spectral information */
e_skip_bytes(p_jpeg, marker_size);
done = true;
@ -1264,11 +1284,22 @@ static int process_markers(struct jpeg* p_jpeg)
case 0xEF: /* Application Field 15*/
case 0xFE: /* Comment */
{
unsigned char id[12];
int marker = c;
marker_size = e_getc(p_jpeg, -1) << 8; /* Highbyte */
marker_size |= e_getc(p_jpeg, -1); /* Lowbyte */
marker_size -= 2;
n = 0;
if (marker == 0xE0 || marker == 0xEE)
{ /* JFIF or Adobe: may say which colour space is used */
n = MIN(marker_size, (int)sizeof(id));
for (i = 0; i < n; i++)
id[i] = e_getc(p_jpeg, -1);
jpeg_app_colorspace(marker, id, n, &p_jpeg->jfif,
&p_jpeg->adobe);
}
JDEBUGF("unhandled marker len %d\n", marker_size);
e_skip_bytes(p_jpeg, marker_size); /* skip segment */
e_skip_bytes(p_jpeg, marker_size - n); /* skip segment */
}
break;
@ -1546,13 +1577,14 @@ INLINE void fix_quant_tables(struct jpeg *p_jpeg)
#ifdef HAVE_LCD_COLOR
const int nc = p_jpeg->blocks > 1 ? 3 : 1;
#else
const int nc = 1; /* chroma is not decoded */
/* chroma is not decoded; RGB is, at the luma scale */
const int nc = p_jpeg->rgb ? 3 : 1;
#endif
for (c = 0; c < nc; c++)
{
int t = p_jpeg->frameheader[c].quanttable_select;
int shift = idct_tbl[p_jpeg->v_scale[!!c]].scale;
int shift = idct_tbl[p_jpeg->v_scale[SCALE_IDX(c)]].scale;
for (f = 0; f < 4; f++) /* a slot already holding t at this scale? */
if (orig[f] == t && want[f] == shift)
@ -1823,6 +1855,9 @@ static struct img_part *store_row_jpeg(void *jpeg_args)
store_offs[p_jpeg->store_pos[3]] = store_offs[1] + store_offs[2];
/* decoded DCT coefficients */
int16_t block[IDCT_WS_SIZE] __attribute__((aligned(8)));
#ifndef HAVE_LCD_COLOR
unsigned char rgb_tmp[2][64]; /* R and G of a greyscale RGB MCU */
#endif
for (x = 0; x < p_jpeg->x_mbl; x++)
{
int blkn;
@ -1830,7 +1865,7 @@ static struct img_part *store_row_jpeg(void *jpeg_args)
{
int ci = p_jpeg->mcu_membership[blkn]; /* component index */
#ifdef JPEG_IDCT_TRANSPOSE
bool transpose = p_jpeg->v_scale[!!ci] > 2;
bool transpose = p_jpeg->v_scale[SCALE_IDX(ci)] > 2;
#endif
int k = 1; /* coefficient index */
int s, r; /* huffman values */
@ -1845,21 +1880,16 @@ static struct img_part *store_row_jpeg(void *jpeg_args)
huff_decode_dc(p_jpeg, dctbl, s, r);
#ifndef HAVE_LCD_COLOR
if (!ci)
if (!ci || p_jpeg->rgb)
#endif
{
s = HUFF_EXTEND(r, s);
#ifdef HAVE_LCD_COLOR
p_jpeg->last_dc_val[ci] += s;
/* output it (assumes zag[0] = 0) */
block[0] = MULTIPLY16(p_jpeg->last_dc_val[ci], qt[0]);
#else
p_jpeg->last_dc_val += s;
/* output it (assumes zag[0] = 0) */
block[0] = MULTIPLY16(p_jpeg->last_dc_val, qt[0]);
#endif
/* coefficient buffer must be cleared */
MEMSET(block+1, 0, p_jpeg->zero_need[!!ci] * sizeof(int));
MEMSET(block+1, 0,
p_jpeg->zero_need[SCALE_IDX(ci)] * sizeof(int));
/* Section F.2.2.2: decode the AC coefficients */
while(true)
{
@ -1870,7 +1900,7 @@ static struct img_part *store_row_jpeg(void *jpeg_args)
if (s)
{
check_bit_buffer(p_jpeg, s);
if (k >= p_jpeg->k_need[!!ci])
if (k >= p_jpeg->k_need[SCALE_IDX(ci)])
goto skip_rest;
r = get_bits(p_jpeg, s);
r = HUFF_EXTEND(r, s);
@ -1912,26 +1942,52 @@ skip_rest:
} /* for k */
block_end:
#ifndef HAVE_LCD_COLOR
if (!ci)
if (!ci || p_jpeg->rgb)
#endif
{
int idct_cols = BIT_N(MIN(p_jpeg->h_scale[!!ci], 3));
int idct_rows = BIT_N(p_jpeg->v_scale[!!ci]);
unsigned char *b_out = out + ci
int sc = SCALE_IDX(ci);
int idct_cols = BIT_N(MIN(p_jpeg->h_scale[sc], 3));
int idct_rows = BIT_N(p_jpeg->v_scale[sc]);
int rowstep = b_width;
#ifdef HAVE_LCD_COLOR
/* Y, Cb, Cr go to bytes 0-2 for the YUV conversion;
R, G, B to their places in struct uint8_rgb */
unsigned char *b_out = out + (p_jpeg->rgb ? 2 - ci : ci)
+ store_offs[p_jpeg->mcu_pos[blkn]];
if (idct_tbl[p_jpeg->v_scale[!!ci]].v_idct)
#else
unsigned char *b_out = out
+ store_offs[p_jpeg->mcu_pos[blkn]];
if (ci < 2 && p_jpeg->rgb)
{ /* keep R and G until B is decoded */
b_out = rgb_tmp[ci];
rowstep = 8;
}
#endif
if (idct_tbl[p_jpeg->v_scale[sc]].v_idct)
#ifdef JPEG_IDCT_TRANSPOSE
idct_tbl[p_jpeg->v_scale[!!ci]].v_idct(block,
idct_tbl[p_jpeg->v_scale[sc]].v_idct(block,
transpose ? block + 8 * idct_cols
: block + idct_cols);
uint16_t * h_block = transpose ? block + 64 : block;
idct_tbl[p_jpeg->h_scale[!!ci]].h_idct(h_block, b_out,
h_block + idct_rows * 8, b_width);
idct_tbl[p_jpeg->h_scale[sc]].h_idct(h_block, b_out,
h_block + idct_rows * 8, rowstep);
#else
idct_tbl[p_jpeg->v_scale[!!ci]].v_idct(block,
idct_tbl[p_jpeg->v_scale[sc]].v_idct(block,
block + idct_cols);
idct_tbl[p_jpeg->h_scale[!!ci]].h_idct(block, b_out,
block + idct_rows * 8, b_width);
idct_tbl[p_jpeg->h_scale[sc]].h_idct(block, b_out,
block + idct_rows * 8, rowstep);
#endif
#ifndef HAVE_LCD_COLOR
if (ci == 2)
{ /* luma from R, G and B (JFIF weights) */
int w = BIT_N(p_jpeg->h_scale[0]);
int xi, yi;
for (yi = 0; yi < idct_rows; yi++, b_out += b_width)
for (xi = 0; xi < w; xi++)
b_out[xi] = (77 * rgb_tmp[0][yi * 8 + xi]
+ 150 * rgb_tmp[1][yi * 8 + xi]
+ 29 * b_out[xi] + 128) >> 8;
}
#endif
}
} /* for blkn */
@ -1958,12 +2014,8 @@ block_end:
{ /* if a restart marker is due: */
p_jpeg->restart = p_jpeg->restart_interval; /* count again */
search_restart(p_jpeg); /* align the bitstream */
#ifdef HAVE_LCD_COLOR
p_jpeg->last_dc_val[0] = p_jpeg->last_dc_val[1] =
p_jpeg->last_dc_val[2] = 0; /* reset decoder */
#else
p_jpeg->last_dc_val = 0;
#endif
}
}
} /* if !p_jpeg->mcu_row */
@ -2268,7 +2320,8 @@ int clip_jpeg_fd(int fd, int flags,
if (resize)
{
if (resize_on_load(bm, dither, &src_dim, &rset, buf_start, maxsize,
cformat, IF_PIX_FMT(p_jpeg->blocks == 1 ? 0 : 1,) store_row_jpeg,
cformat, IF_PIX_FMT(p_jpeg->blocks == 1 || p_jpeg->rgb ? 0 : 1,)
store_row_jpeg,
p_jpeg))
return bm_size;
} else {
@ -2292,7 +2345,7 @@ int clip_jpeg_fd(int fd, int flags,
{
part = store_row_jpeg(p_jpeg);
#ifdef HAVE_LCD_COLOR
if (p_jpeg->blocks > 1)
if (p_jpeg->blocks > 1 && !p_jpeg->rgb)
{
struct uint8_rgb *qp = part->buf;
struct uint8_rgb *end = qp + bm->width;