imageviewer: Initial support for JPEG progressive images. Add decoder

Added jpeg decoder jpegp.c using RAINBOW lib. Currently enabled only for pictures not supported by old decoder (as old decoder more optimized for low mem targets)

Someone TODO:
 * Old decoder has optimized downscale logic which new decoder doesn't have (it gives big difference in required memory and time for decoding). This logic should be ported/adapted if possible.

 * Add smooth downscaling.

 * Grayscale support

Change-Id: Ie96bc62848b51cc6a3942f8e069ec6ab02dc1c56
This commit is contained in:
Roman Artiukhin 2024-10-09 19:39:15 +03:00 committed by Solomon Peachy
parent 64ad7354b6
commit b8238f7b20
19 changed files with 599 additions and 22 deletions

View file

@ -51,6 +51,7 @@ iriverify,viewers
jackpot,games
jewels,games
jpeg,viewers
jpegp,viewers
keybox,apps
keyremap,apps
lamp,apps

View file

@ -3,5 +3,6 @@ jpeg
png
#ifdef HAVE_LCD_COLOR
ppm
jpegp
#endif
gif

View file

@ -25,12 +25,13 @@
static const char *decoder_names[MAX_IMAGE_TYPES] = {
"bmp",
"jpeg",
"jpeg", // Default decoder for jpeg: Use jpeg for old decoder, jpegp for new
"png",
#ifdef HAVE_LCD_COLOR
"ppm",
#endif
"gif"
"gif",
"jpegp",
};
/* Check file type by magic number or file extension

View file

@ -33,6 +33,8 @@ enum image_type {
IMAGE_PPM,
#endif
IMAGE_GIF,
IMAGE_JPEG_PROGRESSIVE,
MAX_IMAGE_TYPES
};

View file

@ -853,6 +853,8 @@ static int load_and_show(char* filename, struct image_info *info)
file_pt[curfile] = NULL;
return change_filename(direction);
}
reload_decoder:
if (image_type != status) /* type of image is changed, load decoder. */
{
struct loader_info loader_info = {
@ -881,6 +883,13 @@ static int load_and_show(char* filename, struct image_info *info)
else
status = imgdec->load_image(filename, info, buf, &remaining);
if (status == PLUGIN_JPEG_PROGRESSIVE)
{
rb->lcd_clear_display();
status = IMAGE_JPEG_PROGRESSIVE;
goto reload_decoder;
}
if (status == PLUGIN_OUTOFMEM)
{
#ifdef USE_PLUG_BUF

View file

@ -55,6 +55,7 @@ enum {
PLUGIN_OTHER = 0x200,
PLUGIN_ABORT,
PLUGIN_OUTOFMEM,
PLUGIN_JPEG_PROGRESSIVE,
ZOOM_IN,
ZOOM_OUT,

View file

@ -170,8 +170,12 @@ static int load_image(char *filename, struct image_info *info,
if (status < 0 || (status & (DQT | SOF0)) != (DQT | SOF0))
{ /* bad format or minimum components not contained */
#ifndef HAVE_LCD_COLOR
rb->splashf(HZ, "unsupported %d", status);
return PLUGIN_ERROR;
#else
return PLUGIN_JPEG_PROGRESSIVE;
#endif
}
if (!(status & DHT)) /* if no Huffman table present: */

View file

@ -0,0 +1,94 @@
/* Simple buffered version of file reader.
* JPEG decoding seems to work faster with it.
* Not fully tested. In case of any issues try FILEGETC (see SOURCES)
* */
#include "rb_glue.h"
static int fd;
static unsigned char buff[256]; //TODO: Adjust it...
static int length = 0;
static int cur_buff_pos = 0;
static int file_pos = 0;
extern int GETC(void)
{
if (cur_buff_pos >= length)
{
length = rb->read(fd, buff, sizeof(buff));
file_pos += length;
cur_buff_pos = 0;
}
return buff[cur_buff_pos++];
}
// multibyte readers: host-endian independent - if evaluated in right order (ie. don't optimize)
extern int GETWbi(void) // 16-bit big-endian
{
return ( GETC()<<8 ) | GETC();
}
extern int GETDbi(void) // 32-bit big-endian
{
return ( GETC()<<24 ) | ( GETC()<<16 ) | ( GETC()<<8 ) | GETC();
}
extern int GETWli(void) // 16-bit little-endian
{
return GETC() | ( GETC()<<8 );
}
extern int GETDli(void) // 32-bit little-endian
{
return GETC() | ( GETC()<<8 ) | ( GETC()<<16 ) | ( GETC()<<24 );
}
// seek
extern void SEEK(int d)
{
int newPos = cur_buff_pos + d;
if (newPos < length && newPos >= 0)
{
cur_buff_pos = newPos;
return;
}
file_pos = rb->lseek(fd, (cur_buff_pos - length) + d, SEEK_CUR);
cur_buff_pos = length = 0;
}
extern void POS(int d)
{
cur_buff_pos = length = 0;
file_pos = d;
rb->lseek(fd, d, SEEK_SET);
}
extern int TELL(void)
{
return file_pos + cur_buff_pos - length;
}
// OPEN/CLOSE file
extern void *OPEN(char *f)
{
printf("Opening %s\n", f);
cur_buff_pos = length = file_pos = 0;
fd = rb->open(f,O_RDONLY);
if ( fd < 0 )
{
printf("Error opening %s\n", f);
return NULL;
}
return &fd;
}
extern int CLOSE(void)
{
cur_buff_pos = length = file_pos = 0;
return rb->close(fd);
}

View file

@ -0,0 +1,71 @@
#include "rb_glue.h"
static int fd;
extern int GETC(void)
{
unsigned char x;
rb->read(fd, &x, 1);
return x;
}
// multibyte readers: host-endian independent - if evaluated in right order (ie. don't optimize)
extern int GETWbi(void) // 16-bit big-endian
{
return ( GETC()<<8 ) | GETC();
}
extern int GETDbi(void) // 32-bit big-endian
{
return ( GETC()<<24 ) | ( GETC()<<16 ) | ( GETC()<<8 ) | GETC();
}
extern int GETWli(void) // 16-bit little-endian
{
return GETC() | ( GETC()<<8 );
}
extern int GETDli(void) // 32-bit little-endian
{
return GETC() | ( GETC()<<8 ) | ( GETC()<<16 ) | ( GETC()<<24 );
}
// seek
extern void SEEK(int d)
{
rb->lseek(fd, d, SEEK_CUR);
}
extern void POS(int d)
{
rb->lseek(fd, d, SEEK_SET);
}
extern int TELL(void)
{
return rb->lseek(fd, 0, SEEK_CUR);
}
// OPEN/CLOSE file
extern void *OPEN(char *f)
{
printf("Opening %s\n", f);
fd = rb->open(f,O_RDONLY);
if ( fd < 0 )
{
printf("Error opening %s\n", f);
return NULL;
}
return &fd;
}
extern int CLOSE(void)
{
return rb->close(fd);
}

View file

@ -37,20 +37,20 @@
// For decoders
extern int GETC();
extern int GETC(void);
// Multibyte helpers
extern int GETWbi(); // read word (16-bit) big-endian
extern int GETWli(); // little-endian
extern int GETDbi(); // read double word (32-bit) big-endian
extern int GETDli(); // little-endian
extern int GETWbi(void); // read word (16-bit) big-endian
extern int GETWli(void); // little-endian
extern int GETDbi(void); // read double word (32-bit) big-endian
extern int GETDli(void); // little-endian
// positioning
extern void SEEK(int); // move relative to current
extern void POS(int); // move absolute position (TIFF)
extern int TELL(); // read actual position
extern int TELL(void); // read actual position
// For RAINBOW clients to implement outside of Rainbow Library
extern void *OPEN(char*);
extern void CLOSE();
extern void CLOSE(void);

View file

@ -0,0 +1,6 @@
jpegp.c
//FILEGETC.c
BUFFILEGETC.c
jpeg81.c
idct.c
mempool.c

View file

@ -40,12 +40,12 @@ jpeg81.c
* SOFTWARE.
*/
#include "GETC.h"
#include "GETC.h"
#include "rb_glue.h"
#include "jpeg81.h"
#include <malloc.h> // calloc() called once
#include <stdio.h> // debug only
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-parameter"
///////////////////////////////////////// LOSSLESS /////////////////////////////////////////
static int P1(struct COMP *C, TSAMP *samp) // Px = Ra
@ -63,17 +63,17 @@ static int P3(struct COMP *C, TSAMP *samp) // Px = Rc
return samp[-C->du_width-1];
}
static int P4(struct COMP *C, TSAMP *samp) // Px = Ra + Rb Rc
static int P4(struct COMP *C, TSAMP *samp) // Px = Ra + Rb - Rc
{
return samp[-1] + samp[-C->du_width] - samp[-C->du_width-1];
}
static int P5(struct COMP *C, TSAMP *samp) // Px = Ra + ((Rb Rc)/2)
static int P5(struct COMP *C, TSAMP *samp) // Px = Ra + ((Rb - Rc)/2)
{
return samp[-1] + ( (samp[-C->du_width] - samp[-C->du_width-1]) >> 1 );
}
static int P6(struct COMP *C, TSAMP *samp) // Px = Rb + ((Ra Rc)/2)
static int P6(struct COMP *C, TSAMP *samp) // Px = Rb + ((Ra - Rc)/2)
{
return samp[-C->du_width] + ( (samp[-1] - samp[-C->du_width-1]) >> 1 );
}
@ -215,7 +215,7 @@ static void du_sequential_huff(struct JPEGD *j, struct COMP *sc, TCOEF *coef)
{
int s, k;
dc_decode_huff(j, sc, coef);
for (k=1; s=sc->ACS[ReadHuffmanCode(j, sc->ACB)]; k++) { // EOB?
for (k=1; (s=sc->ACS[ReadHuffmanCode(j, sc->ACB)]); k++) { // EOB?
k+= s>>4;
if (s==0xf0) continue; // ZRL
coef[k]= ReadDiff(j, s&15);
@ -496,7 +496,7 @@ static void Ri(struct JPEGD *j, int n)
printf("RST%d\n", Marker&7);
printf("%08X: ECS\n", TELL());
}
else printf("STREAM ERROR: expected RSTn missing from ECS\n");
else { printf("STREAM ERROR: expected RSTn missing from ECS\n"); }
j->Reset_decoder(j);
}
}
@ -832,7 +832,7 @@ extern enum JPEGENUM JPEGDecode(struct JPEGD *j)
}
}
printf(" Malloc for %d Data Units (%d bytes)\n\n", TotalDU, sizeof(DU)*TotalDU);
printf(" Malloc for %d Data Units (%lu bytes)\n\n", TotalDU, sizeof(DU)*TotalDU);
if (j->SOF > 0xC8) { // DCT Arithmetic
j->Reset_decoder= Reset_decoder_arith;
@ -953,7 +953,7 @@ extern enum JPEGENUM JPEGDecode(struct JPEGD *j)
for (Lq-=2; Lq; Lq -= 65 + 64*Pq)
{
int (*get)();
int (*get)(void);
int T= GETC();
int Tq= T&3;
int *qt= j->QT[Tq];
@ -993,3 +993,5 @@ extern enum JPEGENUM JPEGDecode(struct JPEGD *j)
}
}
}
#pragma GCC diagnostic pop

View file

@ -144,4 +144,4 @@ struct JPEGD { // The JPEG DECODER OBJECT
};
extern int JPEGDecode(struct JPEGD *j);
extern enum JPEGENUM JPEGDecode(struct JPEGD *j);

View file

@ -0,0 +1,260 @@
#include "jpeg81.h"
#include "idct.h"
#include "GETC.h"
#include "rb_glue.h"
#include "../imageviewer.h"
/**************** begin Application ********************/
/************************* Types ***************************/
struct t_disp
{
unsigned char* bitmap;
};
/************************* Globals ***************************/
/* decompressed image in the possible sizes (1,2,4,8), wasting the other */
static struct t_disp disp[9];
static struct JPEGD jpg; /* too large for stack */
/************************* Implementation ***************************/
static void draw_image_rect(struct image_info *info,
int x, int y, int width, int height)
{
struct t_disp* pdisp = (struct t_disp*)info->data;
#ifdef HAVE_LCD_COLOR
rb->lcd_bitmap_part(
(fb_data*)pdisp->bitmap, info->x + x, info->y + y,
STRIDE(SCREEN_MAIN, info->width, info->height),
x + MAX(0, (LCD_WIDTH-info->width)/2),
y + MAX(0, (LCD_HEIGHT-info->height)/2),
width, height);
#else
mylcd_ub_gray_bitmap_part(
pdisp->bitmap, info->x + x, info->y + y, info->width,
x + MAX(0, (LCD_WIDTH-info->width)/2),
y + MAX(0, (LCD_HEIGHT-info->height)/2),
width, height);
#endif
}
static int img_mem(int ds)
{
struct JPEGD* j = &jpg;
return j->Y/ds * j->X/ds*sizeof(fb_data);
}
/* my memory pool (from the mp3 buffer) */
static char print[32]; /* use a common snprintf() buffer */
static void scaled_dequantization_and_idct(void)
{
struct JPEGD* j = &jpg;
// The following code is based on RAINBOW lib jpeg2bmp example:
// https://github.com/Halicery/vc_rainbow/blob/605c045a564dad8e2df84e48914eac3d2d8d4a9b/jpeg2bmp.c
printf("Scaled de-quantization and IDCT.. ");
int c, i, n;
// Pre-scale quant-tables
int SQ[4][64];
for (c=0; c<4 && j->QT[c][0]; c++)
{
int *q= j->QT[c], *sq= SQ[c];
for (i=0; i<64; i++) sq[i]= q[i] * SCALEM[zigzag[i]];
}
// DEQUANT + IDCT
for (c=0; c<j->Nf; c++)
{
struct COMP *C= j->Components+c;
//int *q= j->QT[C->Qi];
int *sq= SQ[C->Qi];
for (n=0; n < C->du_size; n++)
{
/*
// <--- scaled idct
int k, t[64];
TCOEF *coef= du[x];
t[0]= coef[0] * q[0] + 1024; // dequant DC and level-shift (8-bit)
for (k=1; k<64; k++) t[zigzag[k]] = coef[k] * q[k]; // dequant AC (+zigzag)
idct_s(t, coef);
*/
// <--- scaled idct with dequant
idct_sq( C->du[ (n / C->du_w) * C->du_width + n % C->du_w ], sq );
}
}
printf("done\n");
}
static int load_image(char *filename, struct image_info *info,
unsigned char *buf, ssize_t *buf_size)
{
int status;
struct JPEGD *p_jpg = &jpg;
memset(&disp, 0, sizeof(disp));
memset(&jpg, 0, sizeof(jpg));
init_mem_pool(buf, *buf_size);
if (!OPEN(filename))
{
return PLUGIN_ERROR;
}
if (!iv->running_slideshow)
{
rb->lcd_puts(0, 0, rb->strrchr(filename,'/')+1);
rb->lcd_puts(0, 2, "decoding...");
rb->lcd_update();
}
long time; /* measured ticks */
/* the actual decoding */
time = *rb->current_tick;
status = JPEGDecode(p_jpg);
time = *rb->current_tick - time;
CLOSE();
if (status < 0)
{ /* bad format or minimum components not contained */
if (status == JPEGENUMERR_MALLOC)
{
return PLUGIN_OUTOFMEM;
}
rb->splashf(HZ, "unsupported %d", status);
return PLUGIN_ERROR;
}
if (!iv->running_slideshow)
{
rb->lcd_putsf(0, 2, "image %dx%d", p_jpg->X, p_jpg->Y);
int w, h; /* used to center output */
rb->snprintf(print, sizeof(print), "jpegp %ld.%02ld sec ", time/HZ, time%HZ);
rb->lcd_getstringsize(print, &w, &h); /* centered in progress bar */
rb->lcd_putsxy((LCD_WIDTH - w)/2, LCD_HEIGHT - h, print);
rb->lcd_update();
//rb->sleep(100);
}
info->x_size = p_jpg->X;
info->y_size = p_jpg->Y;
#ifdef DISK_SPINDOWN
if (iv->running_slideshow && iv->immediate_ata_off)
{
/* running slideshow and time is long enough: power down disk */
rb->storage_sleep();
}
#endif
if ( 3 != p_jpg->Nf )
return PLUGIN_ERROR;
scaled_dequantization_and_idct();
*buf_size = freeze_mem_pool();
return PLUGIN_OK;
}
static int get_image(struct image_info *info, int frame, int ds)
{
(void)frame;
struct JPEGD* p_jpg = &jpg;
struct t_disp* p_disp = &disp[ds]; /* short cut */
info->width = p_jpg->X / ds;
info->height = p_jpg->Y / ds;
info->data = p_disp;
if (p_disp->bitmap != NULL)
{
/* we still have it */
return PLUGIN_OK;
}
struct JPEGD* j = p_jpg;
int mem = img_mem(ds);
p_disp->bitmap = malloc(mem);
if (!p_disp->bitmap)
{
clear_mem_pool();
memset(&disp, 0, sizeof(disp));
p_disp->bitmap = malloc(mem);
if (!p_disp->bitmap)
return PLUGIN_ERROR;
}
fb_data *bmp = (fb_data *)p_disp->bitmap;
// The following code is based on RAINBOW lib jpeg2bmp example:
// https://github.com/Halicery/vc_rainbow/blob/605c045a564dad8e2df84e48914eac3d2d8d4a9b/jpeg2bmp.c
// Primitive yuv-rgb converter for all sub-sampling types, 24-bit BMP only
printf("YUV-to-RGB conversion.. ");
int h0 = j->Hmax / j->Components[0].Hi;
int v0 = j->Vmax / j->Components[0].Vi;
int h1 = j->Hmax / j->Components[1].Hi;
int v1 = j->Vmax / j->Components[1].Vi;
int h2 = j->Hmax / j->Components[2].Hi;
int v2 = j->Vmax / j->Components[2].Vi;
int x, y;
for (y = 0; y < j->Y; y++)
{
if (y%ds != 0)
continue;
TCOEF *C0 =
j->Components[0].du[j->Components[0].du_width * ((y / v0) / 8)] + 8 * ((y / v0) & 7);
TCOEF *C1 =
j->Components[1].du[j->Components[1].du_width * ((y / v1) / 8)] + 8 * ((y / v1) & 7);
TCOEF *C2 =
j->Components[2].du[j->Components[2].du_width * ((y / v2) / 8)] + 8 * ((y / v2) & 7);
for (x = 0; x < j->X; x++)
{
if (x%ds != 0)
continue;
TCOEF c0 = C0[(x / h0 / 8) * 64 + ((x / h0) & 7)];
TCOEF c1 = C1[(x / h1 / 8) * 64 + ((x / h1) & 7)];
TCOEF c2 = C2[(x / h2 / 8) * 64 + ((x / h2) & 7)];
// ITU BT.601 full-range YUV-to-RGB integer approximation
{
int y = (c0 << 5) + 16;
int u = c1 - 128;
int v = c2 - 128;
int b = CLIP[(y + 57 * u)>>5]; // B;
int g = CLIP[(y - 11 * u - 23 * v)>>5]; // G
int r = CLIP[(y + 45 * v)>>5]; // R;
*bmp++= FB_RGBPACK(r,g,b);
}
}
}
printf("done\n");
return 0;
}
const struct image_decoder image_decoder = {
false,
img_mem,
load_image,
get_image,
draw_image_rect,
};
IMGDEC_HEADER

View file

@ -0,0 +1,32 @@
# __________ __ ___.
# Open \______ \ ____ ____ | | _\_ |__ _______ ___
# Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
# Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
# Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
# \/ \/ \/ \/ \/
# $Id$
#
JPEGPSRCDIR := $(IMGVSRCDIR)/jpegp
JPEGPBUILDDIR := $(IMGVBUILDDIR)/jpegp
JPEGP_SRC := $(call preprocess, $(JPEGPSRCDIR)/SOURCES)
JPEGP_OBJ := $(call c2obj, $(JPEGP_SRC))
OTHER_SRC += $(JPEGP_SRC)
ROCKS += $(JPEGPBUILDDIR)/jpegp.ovl
$(JPEGPBUILDDIR)/jpegp.refmap: $(JPEGP_OBJ) $(TLSFLIB)
$(JPEGPBUILDDIR)/jpegp.link: $(PLUGIN_LDS) $(JPEGPBUILDDIR)/jpegp.refmap
$(JPEGPBUILDDIR)/jpegp.ovl: $(JPEGP_OBJ) $(TLSFLIB)
JPEGPFLAGS = $(IMGDECFLAGS)
ifndef DEBUG
JPEGPFLAGS += -Os
endif
# Compile plugin with extra flags (adapted from ZXBox)
$(JPEGPBUILDDIR)/%.o: $(JPEGPSRCDIR)/%.c $(JPEGPSRCDIR)/jpegp.make
$(SILENT)mkdir -p $(dir $@)
$(call PRINTS,CC $(subst $(ROOTDIR)/,,$<))$(CC) -I$(dir $<) $(JPEGPFLAGS) -c $< -o $@

View file

@ -0,0 +1,50 @@
#include <inttypes.h>
#include "plugin.h"
static unsigned char *mem_pool;
static unsigned char *mem_pool_start;
static size_t memory_size;
extern void *malloc(size_t size)
{
if (size > memory_size)
return NULL;
memory_size -= size;
unsigned char* ptr = mem_pool;
mem_pool+= size;
return ptr;
}
extern void *calloc(size_t nelem, size_t elem_size)
{
unsigned char* ptr = malloc(nelem*elem_size);
if (!ptr)
return NULL;
rb->memset(ptr, 0, nelem*elem_size);
return ptr;
}
extern void init_mem_pool(const unsigned char *buf, const ssize_t buf_size)
{
//TODO: do we need this alignment? (copied from gif lib)
unsigned char *memory_max;
/* align buffer */
mem_pool_start = mem_pool = (unsigned char *)((intptr_t)(buf + 3) & ~3);
memory_max = (unsigned char *)((intptr_t)(mem_pool + buf_size) & ~3);
memory_size = memory_max - mem_pool;
}
extern ssize_t freeze_mem_pool(void)
{
mem_pool_start = mem_pool;
return memory_size;
}
extern void clear_mem_pool(void)
{
memory_size += mem_pool - mem_pool_start;
mem_pool = mem_pool_start;
}

View file

@ -0,0 +1,7 @@
#include <inttypes.h>
extern void init_mem_pool(const unsigned char *buf, const ssize_t buf_size);
extern void *malloc(size_t size);
extern void *calloc(size_t nelem, size_t elem_size);
extern ssize_t freeze_mem_pool(void);
extern void clear_mem_pool(void);

View file

@ -0,0 +1,35 @@
/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
*
* $Id$
*
* This program 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 2
* of the License, or (at your option) any later version.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
****************************************************************************/
#include "plugin.h"
#include "mempool.h"
//define from rbunicode.h clashes with jpeg81.h struct
#undef COMP
#undef memset
#define memset(a,b,c) rb->memset((a),(b),(c))
#if defined(DEBUG) || defined(SIMULATOR)
#define printf rb->debugf
#else
#undef printf
#define printf(...)
#endif

View file

@ -161,7 +161,8 @@ for JPEG images.
\end{description}
\note{
Progressive scan and other unusual JPEG files are not supported, and will
\opt{lcd_color}{Progressive scan is supported. Unsupported JPEG files}
\nopt{lcd_color}{Progressive scan and other unusual JPEG files are not supported, and} will
result in various ``unsupported xx'' messages. Processing could also fail if the
image is too big to decode which will be explained by a respective message.