Skip to content

Commit faec6f6

Browse files
committed
add support for progressive decoding
1 parent 5c577d7 commit faec6f6

File tree

2 files changed

+134
-72
lines changed

2 files changed

+134
-72
lines changed

src/media.c

Lines changed: 132 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
3434
# include <webp/decode.h>
3535
#endif
3636
#if defined (LAGRANGE_ENABLE_JXL)
37+
# include <the_Foundation/map.h>
3738
# include <jxl/types.h>
3839
# include <jxl/decode.h>
3940
# include <jxl/codestream_header.h>
@@ -140,117 +141,178 @@ static void applyImageStyle_(enum iImageStyle style, iInt2 size, uint8_t *imgDat
140141
}
141142

142143
#if defined (LAGRANGE_ENABLE_JXL)
143-
static uint8_t *loadJxl_(const iBlock *data, int *x, int *y) {
144-
void *runner;
145-
JxlDecoder *dec;
146-
JxlBasicInfo info;
147-
size_t bufsize;
148-
uint8_t *imgData = NULL;
149-
JxlPixelFormat format = {
144+
145+
iDeclareType(StatefulJxlDecoder)
146+
147+
struct Impl_StatefulJxlDecoder {
148+
iMapNode *parent;
149+
iMapNode *child[2];
150+
int flags;
151+
iMapKey key;
152+
153+
JxlDecoder *decoder;
154+
iInt2 imSize;
155+
uint8_t *buffer;
156+
size_t bufferSize;
157+
void *opaqueRunner;
158+
size_t nSeenBytes;
159+
};
160+
161+
iDeclareTypeConstructionArgs(StatefulJxlDecoder, iGmLinkId linkId, int wantedEvents)
162+
163+
void init_StatefulJxlDecoder(iStatefulJxlDecoder *s, iGmLinkId linkId, int wantedEvents) {
164+
memset(s, 0, sizeof(iStatefulJxlDecoder));
165+
166+
s->key = linkId;
167+
s->decoder = JxlDecoderCreate(NULL);
168+
169+
if (!(s->opaqueRunner = JxlResizableParallelRunnerCreate(NULL)))
170+
fprintf(stderr, "[media] JxlResizableParallelRunnerCreate failed\n");
171+
172+
if (s->opaqueRunner &&
173+
JXL_DEC_SUCCESS != JxlDecoderSetParallelRunner(s->decoder, JxlResizableParallelRunner, s->opaqueRunner))
174+
fprintf(stderr, "[media] JxlDecoderSetParallelRunner failed\n");
175+
176+
if (JXL_DEC_SUCCESS != JxlDecoderSubscribeEvents(s->decoder, wantedEvents))
177+
fprintf(stderr, "[media] JxlDecoderSubscribeEvents failed\n");
178+
}
179+
180+
void deinit_StatefulJxlDecoder(iStatefulJxlDecoder *s) {
181+
JxlDecoderDestroy(s->decoder);
182+
JxlResizableParallelRunnerDestroy(s->opaqueRunner);
183+
free(s->buffer);
184+
memset(s, 0, sizeof(iStatefulJxlDecoder));
185+
}
186+
187+
iStatefulJxlDecoder* new_StatefulJxlDecoder(iGmLinkId linkId, int wantedEvents) {
188+
iStatefulJxlDecoder *s = iMalloc(StatefulJxlDecoder);
189+
init_StatefulJxlDecoder(s, linkId, wantedEvents);
190+
return s;
191+
}
192+
193+
void delete_StatefulJxlDecoder(iStatefulJxlDecoder *s) {
194+
deinit_StatefulJxlDecoder(s);
195+
free(s);
196+
}
197+
198+
static int compare_MapNode_(iMapKey a, iMapKey b) {
199+
return (a > b) - (a < b);
200+
}
201+
202+
static uint8_t *loadJxl_(const iBlock *data, iInt2 *imSize, iGmLinkId linkId, iBool isPartial) {
203+
static iMap *decoderMap = NULL;
204+
205+
iStatefulJxlDecoder *s;
206+
JxlBasicInfo info;
207+
JxlDecoderStatus status;
208+
uint8_t *imgData = NULL;
209+
const size_t blockSize = size_Block(data);
210+
const JxlPixelFormat format = {
150211
.num_channels = 4,
151212
.data_type = JXL_TYPE_UINT8,
152213
.endianness = JXL_NATIVE_ENDIAN,
153214
.align = 0
154215
};
155216

156-
if (!(runner = JxlResizableParallelRunnerCreate(NULL))) {
157-
fprintf(stderr, "[media] JxlResizableParallelRunnerCreate failed\n");
158-
goto ret;
159-
}
160-
if (!(dec = JxlDecoderCreate(NULL))) {
161-
fprintf(stderr, "[media] JxlDecoderCreate failed\n");
162-
goto ret;
163-
}
217+
if (!decoderMap && !(decoderMap = new_Map(compare_MapNode_)))
218+
fprintf(stderr, "[media] Creating JxlDecoderMap failed\n");
164219

165-
if (JXL_DEC_SUCCESS !=
166-
JxlDecoderSubscribeEvents(dec, JXL_DEC_BASIC_INFO | JXL_DEC_FULL_IMAGE)) {
167-
fprintf(stderr, "[media] JxlDecoderSubscribeEvents failed\n");
168-
goto ret;
169-
}
170-
171-
if (JXL_DEC_SUCCESS != JxlDecoderSetParallelRunner(dec, JxlResizableParallelRunner, runner)) {
172-
fprintf(stderr, "JxlDecoderSetParallelRunner failed\n");
173-
goto ret;
220+
221+
if (decoderMap && contains_Map(decoderMap, linkId))
222+
s = (iStatefulJxlDecoder*)value_Map(decoderMap, linkId);
223+
else {
224+
s = new_StatefulJxlDecoder(linkId, JXL_DEC_BASIC_INFO | JXL_DEC_FULL_IMAGE);
225+
if (decoderMap && isPartial)
226+
insert_Map(decoderMap, (iMapNode*)s);
174227
}
175228

176-
JxlDecoderSetInput(dec, constData_Block(data), size_Block(data));
177-
JxlDecoderCloseInput(dec);
229+
JxlDecoderSetInput(s->decoder, constData_Block(data) + s->nSeenBytes, blockSize - s->nSeenBytes);
230+
if (!isPartial)
231+
JxlDecoderCloseInput(s->decoder);
178232

179233
while (true) {
180-
switch (JxlDecoderProcessInput(dec)) {
181-
case JXL_DEC_ERROR:
182-
fprintf(stderr, "[media] JXL decoder error\n");
183-
goto ret;
184-
case JXL_DEC_NEED_MORE_INPUT:
185-
fprintf(stderr, "[media] Incomplete JXL data\n");
186-
goto ret;
234+
switch (status = JxlDecoderProcessInput(s->decoder)) {
187235
case JXL_DEC_BASIC_INFO:
188-
if (JXL_DEC_SUCCESS != JxlDecoderGetBasicInfo(dec, &info)) {
236+
if (JXL_DEC_SUCCESS != JxlDecoderGetBasicInfo(s->decoder, &info)) {
189237
fprintf(stderr, "[media] JxlDecoderGetBasicInfo failed\n");
190-
goto ret;
238+
goto err;
191239
}
192-
*x = info.xsize;
193-
*y = info.ysize;
240+
s->imSize = init_I2(info.xsize, info.ysize);
194241
JxlResizableParallelRunnerSetThreads(
195-
runner, JxlResizableParallelRunnerSuggestThreads(*x, *y));
242+
s->opaqueRunner, JxlResizableParallelRunnerSuggestThreads(info.xsize, info.ysize));
196243
break;
197-
case JXL_DEC_COLOR_ENCODING:
198-
break; // don't care
199244
case JXL_DEC_NEED_IMAGE_OUT_BUFFER:
200-
if (JXL_DEC_SUCCESS != JxlDecoderImageOutBufferSize(dec, &format, &bufsize)) {
245+
if (JXL_DEC_SUCCESS != JxlDecoderImageOutBufferSize(s->decoder, &format, &s->bufferSize)) {
201246
fprintf(stderr, "JxlDecoderImageOutBufferSize failed\n");
202-
goto ret;
203-
}
204-
if (bufsize != *x * *y * 4 * sizeof(uint8_t)) {
205-
fprintf(stderr,
206-
"Invalid out buffer size %lu %d\n",
207-
bufsize,
208-
*x * *y * 4 * sizeof(uint8_t));
209-
goto ret;
247+
goto err;
210248
}
211-
imgData = realloc(imgData, bufsize);
249+
s->buffer = realloc(s->buffer, s->bufferSize);
212250
if (JXL_DEC_SUCCESS !=
213-
JxlDecoderSetImageOutBuffer(dec, &format, imgData, bufsize)) {
251+
JxlDecoderSetImageOutBuffer(s->decoder, &format, s->buffer, s->bufferSize)) {
214252
fprintf(stderr, "JxlDecoderSetImageOutBuffer failed\n");
215-
goto ret;
253+
goto err;
216254
}
217255
break;
256+
case JXL_DEC_NEED_MORE_INPUT:
257+
if (!isPartial) {
258+
fprintf(stderr, "[media] Incomplete JXL data\n");
259+
goto err;
260+
}
218261
case JXL_DEC_FULL_IMAGE:
219-
break; // don't care, keep last image
262+
s->nSeenBytes = blockSize - JxlDecoderReleaseInput(s->decoder);
263+
264+
if (status != JXL_DEC_NEED_MORE_INPUT ||
265+
JXL_DEC_SUCCESS == JxlDecoderFlushImage(s->decoder)) {
266+
printf("[media] flushed jxl after %lu bytes\n", s->nSeenBytes);
267+
*imSize = s->imSize;
268+
imgData = malloc(s->bufferSize);
269+
memcpy(imgData, s->buffer, s->bufferSize);
270+
}
271+
272+
goto ret;
220273
case JXL_DEC_SUCCESS:
274+
*imSize = s->imSize;
275+
imgData = s->buffer;
276+
s->buffer = NULL;
221277
goto ret;
278+
case JXL_DEC_ERROR:
279+
fprintf(stderr, "[media] JXL decoder error\n");
280+
goto err;
222281
default:
223282
fprintf(stderr, "[media] JXL unknown decoder status\n");
224-
goto ret;
283+
goto err;
225284
} /* switch (status) */
226285
} /* while */
227286

228287
ret:
229-
if (dec) {
230-
JxlDecoderReleaseInput(dec);
231-
JxlDecoderDestroy(dec);
288+
if (!isPartial) {
289+
err:
290+
if (decoderMap)
291+
remove_Map(decoderMap, s->key);
292+
delete_StatefulJxlDecoder(s);
232293
}
233-
if (runner) JxlResizableParallelRunnerDestroy(runner);
234294

235295
return imgData;
236296
}
237-
#endif
238297

239-
void makeTexture_GmImage(iGmImage *d) {
298+
#endif /* defined (LAGRANGE_ENABLE_JXL) */
299+
300+
iBool makeTexture_GmImage(iGmImage *d, iBool isPartial) {
240301
iBlock *data = &d->partialData;
241302
d->numBytes = size_Block(data);
242303
uint8_t *imgData = NULL;
243-
if (cmp_String(&d->props.mime, "image/webp") == 0) {
304+
iBool isNew = iFalse;
305+
if (!isPartial && cmp_String(&d->props.mime, "image/webp") == 0) {
244306
#if defined (LAGRANGE_ENABLE_WEBP)
245307
imgData = WebPDecodeRGBA(constData_Block(data), size_Block(data), &d->size.x, &d->size.y);
246308
#endif
247309
}
248310
else if (cmp_String(&d->props.mime, "image/jxl") == 0) {
249311
#if defined (LAGRANGE_ENABLE_JXL)
250-
imgData = loadJxl_(data, &d->size.x, &d->size.y);
312+
imgData = loadJxl_(data, &d->size, d->props.linkId, isPartial);
251313
#endif
252314
}
253-
else {
315+
else if (!isPartial) {
254316
imgData = stbi_load_from_memory(
255317
constData_Block(data), (int) size_Block(data), &d->size.x, &d->size.y, NULL, 4);
256318
if (!imgData) {
@@ -303,8 +365,12 @@ void makeTexture_GmImage(iGmImage *d) {
303365
d->texture = SDL_CreateTextureFromSurface(renderer_Window(window), surface);
304366
SDL_FreeSurface(surface);
305367
free(imgData);
368+
isNew = iTrue;
306369
}
307-
clear_Block(data);
370+
if (!isPartial)
371+
clear_Block(data);
372+
373+
return isNew;
308374
}
309375

310376
iDefineTypeConstructionArgs(GmImage, (const iBlock *data), data)
@@ -506,9 +572,7 @@ iBool setData_Media(iMedia *d, iGmLinkId linkId, const iString *mime, const iBlo
506572
img = at_PtrArray(&d->items[image_MediaType], existingIndex);
507573
iAssert(equal_String(&img->props.mime, mime)); /* MIME cannot change */
508574
set_Block(&img->partialData, data);
509-
if (!isPartial) {
510-
makeTexture_GmImage(img);
511-
}
575+
isNew = makeTexture_GmImage(img, isPartial);
512576
}
513577
}
514578
else if (existing.type == audio_MediaType) {
@@ -560,10 +624,7 @@ iBool setData_Media(iMedia *d, iGmLinkId linkId, const iString *mime, const iBlo
560624
img->props.isPermanent = !allowHide;
561625
set_String(&img->props.mime, mime);
562626
pushBack_PtrArray(&d->items[image_MediaType], img);
563-
if (!isPartial) {
564-
makeTexture_GmImage(img);
565-
}
566-
isNew = iTrue;
627+
isNew = makeTexture_GmImage(img, isPartial);
567628
}
568629
else if (startsWith_String(mime, "audio/")) {
569630
#if defined (LAGRANGE_ENABLE_AUDIO)

src/ui/documentwidget.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2447,7 +2447,8 @@ static iBool handleMediaCommand_DocumentWidget_(iDocumentWidget *d, const char *
24472447
if (isSuccess_GmStatusCode(code)) {
24482448
iGmResponse *resp = lockResponse_GmRequest(req->req);
24492449
if (isDownloadRequest_DocumentWidget(d, req) ||
2450-
startsWith_String(&resp->meta, "audio/")) {
2450+
startsWith_String(&resp->meta, "audio/") ||
2451+
startsWith_String(&resp->meta, "image/")) {
24512452
/* TODO: Use a helper? This is same as below except for the partialData flag. */
24522453
if (setData_Media(media_GmDocument(d->view->doc),
24532454
req->linkId,

0 commit comments

Comments
 (0)