Skip to content

Commit e0d5675

Browse files
committed
FEAT: PNG's pre-compression filter algorithms exposed as filter and unfilter natives
1 parent 3d581b3 commit e0d5675

File tree

4 files changed

+290
-2
lines changed

4 files changed

+290
-2
lines changed

src/core/u-compress.c

+227-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
** REBOL [R3] Language Interpreter and Run-time Environment
44
**
55
** Copyright 2012 REBOL Technologies
6+
** Copyright 2012-2021 Rebol Open Source Contributors
67
** REBOL is a trademark of REBOL Technologies
78
**
89
** Licensed under the Apache License, Version 2.0 (the "License");
@@ -340,4 +341,229 @@ static const ISzAlloc g_Alloc = { SzAlloc, SzFree };
340341
return output;
341342
}
342343

343-
#endif //INCLUDE_LZMA
344+
#endif //INCLUDE_LZMA
345+
346+
347+
348+
#ifdef INCLUDE_PNG_CODEC
349+
int paeth_predictor(int a, int b, int c);
350+
#else
351+
#define int_abs(a) (((a)<0)?(-(a)):(a))
352+
static int paeth_predictor(int a, int b, int c) {
353+
int p, pa, pb, pc;
354+
355+
p = a + b - c;
356+
pa = int_abs(p - a);
357+
pb = int_abs(p - b);
358+
pc = int_abs(p - c);
359+
if ((pa <= pb) && (pa <= pc))
360+
return a;
361+
else if (pb <= pc)
362+
return b;
363+
return c;
364+
}
365+
#endif
366+
367+
enum PNG_Filter_Types {
368+
PNG_FILTER_SUB = 1,
369+
PNG_FILTER_UP,
370+
PNG_FILTER_AVERAGE,
371+
PNG_FILTER_PAETH
372+
};
373+
374+
static REBYTE get_png_filter_type(REBVAL* val) {
375+
if (IS_WORD(val)) {
376+
switch (VAL_WORD_SYM(val)) {
377+
case SYM_SUB: return PNG_FILTER_SUB;
378+
case SYM_UP: return PNG_FILTER_UP;
379+
case SYM_AVERAGE: return PNG_FILTER_AVERAGE;
380+
case SYM_PAETH: return PNG_FILTER_PAETH;
381+
}
382+
}
383+
else if (IS_INTEGER(val)) {
384+
return MIN(PNG_FILTER_PAETH, MAX(0, VAL_INT32(val)));
385+
}
386+
Trap1(RE_INVALID_ARG, val);
387+
}
388+
389+
// See: https://www.rfc-editor.org/rfc/rfc2083.html#page-31
390+
// https://en.wikipedia.org/wiki/Portable_Network_Graphics#Filtering
391+
/***********************************************************************
392+
**
393+
*/ REBNATIVE(filter)
394+
/*
395+
// filter: native [
396+
// "PNG delta filter"
397+
// data [binary!] "Input"
398+
// width [number!] "Scanline width"
399+
// type [integer! word!] "1..4 or one of: [sub up average paeth]"
400+
// /skip bpp [integer!] "Bytes per pixel"
401+
// ]
402+
***********************************************************************/
403+
{
404+
REBVAL *val_data = D_ARG(1);
405+
REBVAL *val_width = D_ARG(2);
406+
REBVAL *val_type = D_ARG(3);
407+
REBOOL ref_skip = D_REF(4);
408+
REBVAL *val_bpp = D_ARG(5);
409+
410+
REBSER *ser;
411+
REBYTE *bin = (REBCHR *)VAL_BIN_DATA(val_data);
412+
REBCNT r, c, rows, bytes;
413+
REBYTE *scan, *prev, *temp, *out;
414+
REBINT width = AS_INT32(val_width);
415+
REBYTE filter = get_png_filter_type(val_type);
416+
REBCNT bpp = ref_skip ? VAL_INT32(val_bpp) : 1;
417+
418+
bytes = VAL_LEN(val_data);
419+
420+
if (width <= 1 || width > bytes)
421+
Trap1(RE_INVALID_ARG, val_width);
422+
if (bpp < 1 || bpp > width)
423+
Trap1(RE_INVALID_ARG, val_bpp);
424+
425+
rows = bytes / width;
426+
ser = Make_Binary(bytes);
427+
out = BIN_DATA(ser);
428+
429+
temp = malloc(width);
430+
if (!temp) {
431+
Trap0(RE_NO_MEMORY);
432+
return R_NONE;
433+
}
434+
memset(temp, 0, width);
435+
436+
prev = temp;
437+
for (r = 0; r < rows; r++) {
438+
scan = bin + (r * width);
439+
out = BIN_SKIP(ser, r * width);
440+
441+
switch (filter) {
442+
case PNG_FILTER_SUB:
443+
for (c = 0; c < bpp; c++)
444+
out[c] = scan[c];
445+
for (c = bpp; c < width; c++)
446+
out[c] = scan[c] - scan[c - bpp];
447+
break;
448+
case PNG_FILTER_UP:
449+
for (c = 0; c < width; c++)
450+
out[c] = scan[c] - prev[c];
451+
break;
452+
case PNG_FILTER_AVERAGE:
453+
for (c = 0; c < bpp; c++)
454+
out[c] = scan[c] - (prev[c] >> 1);
455+
for (c = bpp; c < width; c++)
456+
out[c] = scan[c] - ((scan[c - bpp] + prev[c]) >> 1) & 0xFF;
457+
break;
458+
case PNG_FILTER_PAETH:
459+
for (c = 0; c < bpp; c++)
460+
out[c] = scan[c] - prev[c];
461+
for (c = bpp; c < width; c++)
462+
out[c] = scan[c] - paeth_predictor(scan[c - bpp], prev[c], prev[c - bpp]);
463+
break;
464+
}
465+
prev = scan;
466+
}
467+
free(temp);
468+
SET_BINARY(D_RET, ser);
469+
VAL_TAIL(D_RET) = bytes;
470+
return R_RET;
471+
}
472+
473+
/***********************************************************************
474+
**
475+
*/ REBNATIVE(unfilter)
476+
/*
477+
// unfilter: native [
478+
// "Reversed PNG delta filter"
479+
// data [binary!] "Input"
480+
// width [number!] "Scanline width (not counting the type byte)"
481+
// /as "Filter type. If not used, type is decoded from first byte on each line."
482+
// type [integer! word!] "1..4 or one of: [sub up average paeth]"
483+
// /skip
484+
// bpp [integer!] "Bytes per pixel"
485+
// ]
486+
***********************************************************************/
487+
{
488+
REBVAL *val_data = D_ARG(1);
489+
REBVAL *val_width = D_ARG(2);
490+
REBOOL ref_as = D_REF(3);
491+
REBVAL *val_type = D_ARG(4);
492+
REBOOL ref_skip = D_REF(5);
493+
REBVAL *val_bpp = D_ARG(6);
494+
495+
REBSER *ser;
496+
REBYTE *bin = VAL_BIN_DATA(val_data);
497+
REBINT width = AS_INT32(val_width);
498+
REBCNT r, c, rows;
499+
REBYTE *scan, *prev, *temp, *out;
500+
REBYTE filter;
501+
REBCNT bytes = VAL_LEN(val_data);
502+
REBCNT bpp = ref_skip ? VAL_INT32(val_bpp) : 1;
503+
504+
if (!ref_as) width++;
505+
if (width <= 1 || width > bytes)
506+
Trap1(RE_INVALID_ARG, val_width);
507+
if (bpp < 1 || bpp > width)
508+
Trap1(RE_INVALID_ARG, val_bpp);
509+
510+
rows = ceil(bytes / width);
511+
ser = Make_Binary(bytes);
512+
out = BIN_DATA(ser);
513+
514+
if (ref_as)
515+
filter = get_png_filter_type(val_type);
516+
517+
temp = malloc(width);
518+
if (!temp) {
519+
Trap0(RE_NO_MEMORY);
520+
return R_NONE;
521+
}
522+
memset(temp, 0, width);
523+
524+
if (!ref_as) width--;
525+
526+
prev = temp;
527+
for (r = 0; r < rows; r++) {
528+
if (ref_as) {
529+
scan = bin + r * width;
530+
out = BIN_SKIP(ser, r * width);
531+
} else {
532+
scan = bin + r * (width + 1);
533+
out = BIN_SKIP(ser, r * (width));
534+
filter = scan[0];
535+
scan++;
536+
//out++;
537+
}
538+
539+
switch (filter) {
540+
case PNG_FILTER_SUB:
541+
for (c = 0; c < bpp; c++)
542+
out[c] = scan[c];
543+
for (c = bpp; c < width; c++)
544+
out[c] = scan[c] + out[c - bpp];
545+
break;
546+
case PNG_FILTER_UP:
547+
for (c = 0; c < width; c++)
548+
out[c] = scan[c] + prev[c];
549+
break;
550+
case PNG_FILTER_AVERAGE:
551+
for (c = 0; c < bpp; c++)
552+
out[c] = scan[c] + (prev[c] >> 1) & 0xFF;
553+
for (c = bpp; c < width; c++)
554+
out[c] = scan[c] + ((out[c - bpp] + prev[c]) >> 1) & 0xFF;
555+
break;
556+
case PNG_FILTER_PAETH:
557+
for (c = 0; c < bpp; c++)
558+
out[c] = scan[c] + prev[c];
559+
for (c = bpp; c < width; c++)
560+
out[c] = scan[c] + paeth_predictor(out[c - bpp], prev[c], prev[c - bpp]);
561+
break;
562+
}
563+
prev = out;
564+
}
565+
free(temp);
566+
SET_BINARY(D_RET, ser);
567+
VAL_TAIL(D_RET) = ref_as ? bytes : bytes - rows;
568+
return R_RET;
569+
}

src/core/u-png.c

+1-1
Original file line numberDiff line numberDiff line change
@@ -474,7 +474,7 @@ static void process_row_6_16(unsigned char *p,int width,int r,int hoff,int hskip
474474
}
475475
}
476476

477-
static int paeth_predictor(int a,int b,int c) {
477+
int paeth_predictor(int a,int b,int c) {
478478
int p,pa,pb,pc;
479479

480480
p=a+b-c;

src/include/sys-value.h

+2
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,8 @@ typedef struct Reb_Type {
146146
#define SET_CHAR(v,n) VAL_SET(v, REB_CHAR), VAL_CHAR(v) = (REBUNI)(n)
147147

148148
#define IS_NUMBER(v) (VAL_TYPE(v) == REB_INTEGER || VAL_TYPE(v) == REB_DECIMAL)
149+
#define AS_INT32(v) (IS_INTEGER(v) ? VAL_INT32(v) : (REBINT)VAL_DECIMAL(v))
150+
#define AS_INT64(v) (IS_INTEGER(v) ? VAL_INT64(v) : (REBI64)VAL_DECIMAL(v))
149151

150152

151153
/***********************************************************************

src/tests/units/compress-test.r3

+60
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,66 @@ data: "test test test"
129129

130130
===end-group===
131131

132+
===start-group=== "PNG Pre-compression"
133+
bin: #{01020304050102030405}
134+
--test-- "FILTER 2"
135+
--assert #{0101030105FC02010401} = filter bin 2 'sub
136+
--assert #{0102020202FDFD020202} = filter bin 2 'up
137+
--assert #{0102030204FD00020302} = filter bin 2 'average
138+
--assert #{0101020102FCFD020201} = filter bin 2 'paeth
139+
--test-- "FILTER 5"
140+
--assert #{01010101010101010101} = filter bin 5 'sub
141+
--assert #{01020304050000000000} = filter bin 5 'up
142+
--assert #{01020203030101010101} = filter bin 5 'average
143+
--assert #{01010101010000000000} = filter bin 5 'paeth
144+
--test-- "FILTER 10"
145+
--assert #{0101010101FC01010101} = filter bin 10 'sub
146+
--assert #{01020304050102030405} = filter bin 10 'up
147+
--assert #{0102020303FF02020303} = filter bin 10 'average
148+
--assert #{0101010101FC01010101} = filter bin 10 'paeth
149+
--test-- "UNFILTER/AS 2"
150+
--assert bin = unfilter/as #{0101030105FC02010401} 2 'sub
151+
--assert bin = unfilter/as #{0102020202FDFD020202} 2 'up
152+
--assert bin = unfilter/as #{0102030204FD00020302} 2 'average
153+
--assert bin = unfilter/as #{0101020102FCFD020201} 2 'paeth
154+
--test-- "UNFILTER/AS 5"
155+
--assert bin = unfilter/as #{01010101010101010101} 5 'sub
156+
--assert bin = unfilter/as #{01020304050000000000} 5 'up
157+
--assert bin = unfilter/as #{01020203030101010101} 5 'average
158+
--assert bin = unfilter/as #{01010101010000000000} 5 'paeth
159+
--test-- "UNFILTER/AS 10"
160+
--assert bin = unfilter/as #{0101010101FC01010101} 10 'sub
161+
--assert bin = unfilter/as #{01020304050102030405} 10 'up
162+
--assert bin = unfilter/as #{0102020303FF02020303} 10 'average
163+
--assert bin = unfilter/as #{0101010101FC01010101} 10 'paeth
164+
--test-- "UNFILTER 2"
165+
--assert bin = unfilter #{01 0101 01 0301 01 05FC 01 0201 01 0401} 2
166+
--assert bin = unfilter #{02 0102 02 0202 02 02FD 02 FD02 02 0202} 2
167+
--assert bin = unfilter #{03 0102 03 0302 03 04FD 03 0002 03 0302} 2
168+
--assert bin = unfilter #{04 0101 04 0201 04 02FC 04 FD02 04 0201} 2
169+
--test-- "UNFILTER 5"
170+
--assert bin = unfilter #{01 0101010101 01 0101010101} 5
171+
--assert bin = unfilter #{02 0102030405 02 0000000000} 5
172+
--assert bin = unfilter #{03 0102020303 03 0101010101} 5
173+
--assert bin = unfilter #{04 0101010101 04 0000000000} 5
174+
--test-- "UNFILTER 10"
175+
--assert bin = unfilter #{01 0101010101FC01010101} 10
176+
--assert bin = unfilter #{02 01020304050102030405} 10
177+
--assert bin = unfilter #{03 0102020303FF02020303} 10
178+
--assert bin = unfilter #{04 0101010101FC01010101} 10
179+
180+
bin: #{010203FF010203FF 020304FF030405FF}
181+
--test-- "FILTER/SKIP"
182+
--assert #{010203FF00000000020304FF01010100} = b1: filter/skip bin 8 'sub 4
183+
--assert #{010203FF010203FF0101010002020200} = b2: filter/skip bin 8 'up 4
184+
--assert #{010203FF010102800202038002020200} = b3: filter/skip bin 8 'average 4
185+
--assert #{010203FF000000000101010001010100} = b4: filter/skip bin 8 'paeth 4
186+
--test-- "FILTER/AS/SKIP"
187+
--assert bin = unfilter/as/skip :b1 8 'sub 4
188+
--assert bin = unfilter/as/skip :b2 8 'up 4
189+
--assert bin = unfilter/as/skip :b3 8 'average 4
190+
--assert bin = unfilter/as/skip :b4 8 'paeth 4
191+
===end-group===
132192

133193

134194
~~~end-file~~~

0 commit comments

Comments
 (0)