Resurrect old nim-native jpeg parser and fix most issues (#414)
* Add new jpeg tests. * Resurrect old jpeg. * Fix Jpeg grayscale * Fix 4:1:1 scaling. * Better chunk scanner. * Fight progressive. * Fight restart markers. * Jpeg can now parse all of the test files. * Fuzzing and overflow checks * Make getBits* the only way to read the bits. * Make Quantiziation and IDCT happen only in one place. * Rename jpg folder to jpeg
1
.gitignore
vendored
|
@ -14,3 +14,4 @@ __pycache__
|
||||||
bindings/generated
|
bindings/generated
|
||||||
*.dSYM
|
*.dSYM
|
||||||
dump.txt
|
dump.txt
|
||||||
|
tests/fileformats/jpeg/generated
|
||||||
|
|
1137
src/pixie/fileformats/jpeg.nim
Normal file
|
@ -1,15 +1,17 @@
|
||||||
import pixie/common, pixie/images
|
import pixie/common, pixie/images
|
||||||
|
|
||||||
when defined(pixieUseStb):
|
|
||||||
import pixie/fileformats/stb_image/stb_image
|
|
||||||
|
|
||||||
const
|
const
|
||||||
jpgStartOfImage* = [0xFF.uint8, 0xD8]
|
jpgStartOfImage* = [0xFF.uint8, 0xD8]
|
||||||
|
|
||||||
|
when defined(pixieUseStb):
|
||||||
|
import pixie/fileformats/stb_image/stb_image
|
||||||
|
else:
|
||||||
|
import pixie/fileformats/jpeg
|
||||||
|
|
||||||
proc decodeJpg*(data: string): Image {.inline, raises: [PixieError].} =
|
proc decodeJpg*(data: string): Image {.inline, raises: [PixieError].} =
|
||||||
## Decodes the JPEG into an Image.
|
## Decodes the JPEG into an Image.
|
||||||
when not defined(pixieUseStb):
|
when not defined(pixieUseStb):
|
||||||
raise newException(PixieError, "Decoding JPG requires -d:pixieUseStb")
|
decodeJpeg(data)
|
||||||
else:
|
else:
|
||||||
var
|
var
|
||||||
width: int
|
width: int
|
||||||
|
|
|
@ -5,7 +5,7 @@ import
|
||||||
test_gif,
|
test_gif,
|
||||||
test_images,
|
test_images,
|
||||||
test_images_draw,
|
test_images_draw,
|
||||||
test_jpg,
|
test_jpeg,
|
||||||
test_masks,
|
test_masks,
|
||||||
test_paints,
|
test_paints,
|
||||||
test_paths,
|
test_paths,
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import benchy, pixie/fileformats/jpg
|
import benchy, pixie/fileformats/jpg
|
||||||
|
|
||||||
let data = readFile("tests/fileformats/jpg/jpeg420exif.jpg")
|
let data = readFile("tests/fileformats/jpeg/jpeg420exif.jpg")
|
||||||
|
|
||||||
timeIt "pixie decode":
|
timeIt "pixie decode":
|
||||||
discard decodeJpg(data)
|
discard decodeJpg(data)
|
BIN
tests/fileformats/jpeg/master/16x16.jpg
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
tests/fileformats/jpeg/master/16x16.png
Normal file
After Width: | Height: | Size: 626 B |
BIN
tests/fileformats/jpeg/master/16x16_progressive.jpg
Normal file
After Width: | Height: | Size: 1.6 KiB |
BIN
tests/fileformats/jpeg/master/8x8.jpg
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
tests/fileformats/jpeg/master/8x8.png
Normal file
After Width: | Height: | Size: 589 B |
BIN
tests/fileformats/jpeg/master/8x8_progressive.jpg
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
tests/fileformats/jpeg/master/black.jpg
Normal file
After Width: | Height: | Size: 636 B |
BIN
tests/fileformats/jpeg/master/blue.jpg
Normal file
After Width: | Height: | Size: 639 B |
BIN
tests/fileformats/jpeg/master/cat.png
Normal file
After Width: | Height: | Size: 52 KiB |
BIN
tests/fileformats/jpeg/master/cat_4_1_1.jpg
Normal file
After Width: | Height: | Size: 9.6 KiB |
BIN
tests/fileformats/jpeg/master/cat_4_2_0.jpg
Normal file
After Width: | Height: | Size: 8.7 KiB |
BIN
tests/fileformats/jpeg/master/cat_4_2_2.jpg
Normal file
After Width: | Height: | Size: 9.3 KiB |
BIN
tests/fileformats/jpeg/master/cat_4_4_4.jpg
Normal file
After Width: | Height: | Size: 10 KiB |
BIN
tests/fileformats/jpeg/master/cat_4_4_4_progressive.jpg
Normal file
After Width: | Height: | Size: 10 KiB |
BIN
tests/fileformats/jpeg/master/cat_restart_markers_5.jpg
Normal file
After Width: | Height: | Size: 10 KiB |
After Width: | Height: | Size: 10 KiB |
BIN
tests/fileformats/jpeg/master/exif_overrun.jpg
Normal file
After Width: | Height: | Size: 5.2 KiB |
BIN
tests/fileformats/jpeg/master/grayscale_test.jpg
Normal file
After Width: | Height: | Size: 38 KiB |
BIN
tests/fileformats/jpeg/master/green.jpg
Normal file
After Width: | Height: | Size: 639 B |
BIN
tests/fileformats/jpeg/master/mandrill.jpg
Normal file
After Width: | Height: | Size: 75 KiB |
BIN
tests/fileformats/jpeg/master/progressive.jpg
Normal file
After Width: | Height: | Size: 285 KiB |
BIN
tests/fileformats/jpeg/master/quality_01.jpg
Normal file
After Width: | Height: | Size: 2.8 KiB |
BIN
tests/fileformats/jpeg/master/quality_10.jpg
Normal file
After Width: | Height: | Size: 2.7 KiB |
BIN
tests/fileformats/jpeg/master/quality_100.jpg
Normal file
After Width: | Height: | Size: 3.3 KiB |
BIN
tests/fileformats/jpeg/master/quality_25.jpg
Normal file
After Width: | Height: | Size: 2.8 KiB |
BIN
tests/fileformats/jpeg/master/quality_50.jpg
Normal file
After Width: | Height: | Size: 3.4 KiB |
BIN
tests/fileformats/jpeg/master/red.jpg
Normal file
After Width: | Height: | Size: 639 B |
BIN
tests/fileformats/jpeg/master/testimg.jpg
Normal file
After Width: | Height: | Size: 5.6 KiB |
BIN
tests/fileformats/jpeg/master/testimgp.jpg
Normal file
After Width: | Height: | Size: 5.5 KiB |
BIN
tests/fileformats/jpeg/master/testorig.jpg
Normal file
After Width: | Height: | Size: 5.6 KiB |
BIN
tests/fileformats/jpeg/master/testprog.jpg
Normal file
After Width: | Height: | Size: 5.5 KiB |
BIN
tests/fileformats/jpeg/master/white.jpg
Normal file
After Width: | Height: | Size: 634 B |
Before Width: | Height: | Size: 751 KiB |
65
tests/fuzz_jpeg.nim
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
import pixie/common, pixie/fileformats/jpg, random, strformat
|
||||||
|
|
||||||
|
randomize()
|
||||||
|
|
||||||
|
var files = @[
|
||||||
|
"tests/fileformats/jpeg/master/red.jpg",
|
||||||
|
"tests/fileformats/jpeg/master/green.jpg",
|
||||||
|
"tests/fileformats/jpeg/master/blue.jpg",
|
||||||
|
"tests/fileformats/jpeg/master/white.jpg",
|
||||||
|
"tests/fileformats/jpeg/master/black.jpg",
|
||||||
|
|
||||||
|
"tests/fileformats/jpeg/master/8x8.jpg",
|
||||||
|
"tests/fileformats/jpeg/master/8x8_progressive.jpg",
|
||||||
|
|
||||||
|
"tests/fileformats/jpeg/master/16x16.jpg",
|
||||||
|
"tests/fileformats/jpeg/master/16x16_progressive.jpg",
|
||||||
|
|
||||||
|
"tests/fileformats/jpeg/master/quality_01.jpg",
|
||||||
|
"tests/fileformats/jpeg/master/quality_10.jpg",
|
||||||
|
"tests/fileformats/jpeg/master/quality_25.jpg",
|
||||||
|
"tests/fileformats/jpeg/master/quality_50.jpg",
|
||||||
|
"tests/fileformats/jpeg/master/quality_100.jpg",
|
||||||
|
|
||||||
|
"tests/fileformats/jpeg/master/cat_4_4_4.jpg",
|
||||||
|
"tests/fileformats/jpeg/master/cat_4_4_4.jpg",
|
||||||
|
"tests/fileformats/jpeg/master/cat_4_2_2.jpg",
|
||||||
|
"tests/fileformats/jpeg/master/cat_4_2_0.jpg",
|
||||||
|
"tests/fileformats/jpeg/master/cat_4_1_1.jpg",
|
||||||
|
"tests/fileformats/jpeg/master/cat_4_4_4_progressive.jpg",
|
||||||
|
"tests/fileformats/jpeg/master/cat_restart_markers_5.jpg",
|
||||||
|
"tests/fileformats/jpeg/master/cat_restart_markers_5_progressive.jpg",
|
||||||
|
|
||||||
|
"tests/fileformats/jpeg/master/mandrill.jpg",
|
||||||
|
|
||||||
|
"tests/fileformats/jpeg/master/exif_overrun.jpg",
|
||||||
|
"tests/fileformats/jpeg/master/grayscale_test.jpg",
|
||||||
|
"tests/fileformats/jpeg/master/progressive.jpg",
|
||||||
|
|
||||||
|
"tests/fileformats/jpeg/master/testimg.jpg",
|
||||||
|
"tests/fileformats/jpeg/master/testimgp.jpg",
|
||||||
|
"tests/fileformats/jpeg/master/testorig.jpg",
|
||||||
|
"tests/fileformats/jpeg/master/testprog.jpg",
|
||||||
|
]
|
||||||
|
|
||||||
|
for i in 0 ..< 10_000:
|
||||||
|
let original = readFile(sample(files))
|
||||||
|
|
||||||
|
var data = original
|
||||||
|
let
|
||||||
|
pos = rand(0 ..< data.len)
|
||||||
|
value = rand(255).uint8
|
||||||
|
data[pos] = value.char
|
||||||
|
echo &"{i} {pos} {value}"
|
||||||
|
|
||||||
|
try:
|
||||||
|
let img = decodeJpg(data)
|
||||||
|
doAssert img.height > 0 and img.width > 0
|
||||||
|
except PixieError:
|
||||||
|
discard
|
||||||
|
data = data[0 ..< pos]
|
||||||
|
try:
|
||||||
|
let img = decodeJpg(data)
|
||||||
|
doAssert img.height > 0 and img.width > 0
|
||||||
|
except PixieError:
|
||||||
|
discard
|
|
@ -1,25 +0,0 @@
|
||||||
import pixie/common, pixie/fileformats/jpg, random, strformat
|
|
||||||
|
|
||||||
randomize()
|
|
||||||
|
|
||||||
let original = cast[seq[uint8]](readFile("tests/fileformats/jpg/jpeg420exif.jpg"))
|
|
||||||
|
|
||||||
for i in 0 ..< 10_000:
|
|
||||||
var data = original
|
|
||||||
let
|
|
||||||
pos = rand(data.len)
|
|
||||||
value = rand(255).uint8
|
|
||||||
data[pos] = value
|
|
||||||
echo &"{i} {pos} {value}"
|
|
||||||
try:
|
|
||||||
let img = decodeJpg(data)
|
|
||||||
doAssert img.height > 0 and img.width > 0
|
|
||||||
except PixieError:
|
|
||||||
discard
|
|
||||||
|
|
||||||
data = data[0 ..< pos]
|
|
||||||
try:
|
|
||||||
let img = decodeJpg(data)
|
|
||||||
doAssert img.height > 0 and img.width > 0
|
|
||||||
except PixieError:
|
|
||||||
discard
|
|
46
tests/test_jpeg.nim
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
import pixie, strutils
|
||||||
|
|
||||||
|
import pixie/fileformats/jpg
|
||||||
|
|
||||||
|
var files = @[
|
||||||
|
"tests/fileformats/jpeg/master/red.jpg",
|
||||||
|
"tests/fileformats/jpeg/master/green.jpg",
|
||||||
|
"tests/fileformats/jpeg/master/blue.jpg",
|
||||||
|
"tests/fileformats/jpeg/master/white.jpg",
|
||||||
|
"tests/fileformats/jpeg/master/black.jpg",
|
||||||
|
|
||||||
|
"tests/fileformats/jpeg/master/8x8.jpg",
|
||||||
|
"tests/fileformats/jpeg/master/8x8_progressive.jpg",
|
||||||
|
|
||||||
|
"tests/fileformats/jpeg/master/16x16.jpg",
|
||||||
|
"tests/fileformats/jpeg/master/16x16_progressive.jpg",
|
||||||
|
|
||||||
|
"tests/fileformats/jpeg/master/quality_01.jpg",
|
||||||
|
"tests/fileformats/jpeg/master/quality_10.jpg",
|
||||||
|
"tests/fileformats/jpeg/master/quality_25.jpg",
|
||||||
|
"tests/fileformats/jpeg/master/quality_50.jpg",
|
||||||
|
"tests/fileformats/jpeg/master/quality_100.jpg",
|
||||||
|
|
||||||
|
"tests/fileformats/jpeg/master/cat_4_4_4.jpg",
|
||||||
|
"tests/fileformats/jpeg/master/cat_4_4_4.jpg",
|
||||||
|
"tests/fileformats/jpeg/master/cat_4_2_2.jpg",
|
||||||
|
"tests/fileformats/jpeg/master/cat_4_2_0.jpg",
|
||||||
|
"tests/fileformats/jpeg/master/cat_4_1_1.jpg",
|
||||||
|
"tests/fileformats/jpeg/master/cat_4_4_4_progressive.jpg",
|
||||||
|
"tests/fileformats/jpeg/master/cat_restart_markers_5.jpg",
|
||||||
|
"tests/fileformats/jpeg/master/cat_restart_markers_5_progressive.jpg",
|
||||||
|
|
||||||
|
"tests/fileformats/jpeg/master/mandrill.jpg",
|
||||||
|
"tests/fileformats/jpeg/master/exif_overrun.jpg",
|
||||||
|
"tests/fileformats/jpeg/master/grayscale_test.jpg",
|
||||||
|
"tests/fileformats/jpeg/master/progressive.jpg",
|
||||||
|
|
||||||
|
"tests/fileformats/jpeg/master/testimg.jpg",
|
||||||
|
"tests/fileformats/jpeg/master/testimgp.jpg",
|
||||||
|
"tests/fileformats/jpeg/master/testorig.jpg",
|
||||||
|
"tests/fileformats/jpeg/master/testprog.jpg",
|
||||||
|
]
|
||||||
|
|
||||||
|
for file in files:
|
||||||
|
var img = decodeJpg(readFile(file))
|
||||||
|
# img.writeFile(file.replace("master", "generated").replace(".jpg", ".jpeg.png"))
|
|
@ -1,6 +0,0 @@
|
||||||
when defined(pixieUseStb):
|
|
||||||
import pixie/fileformats/jpg
|
|
||||||
|
|
||||||
let
|
|
||||||
original = readFile("tests/fileformats/jpg/jpeg420exif.jpg")
|
|
||||||
stbDecoded = decodeJpg(original)
|
|
|
@ -1,3 +1,3 @@
|
||||||
import pixie/fileformats/jpg, pixie/fileformats/stb_image/stb_image
|
import pixie/fileformats/jpg, pixie/fileformats/stb_image/stb_image
|
||||||
|
|
||||||
let original = readFile("tests/fileformats/jpg/jpeg420exif.jpg")
|
let original = readFile("tests/fileformats/jpeg/jpeg420exif.jpg")
|