diff --git a/dither-image.js b/dither-image.js index 93fddd9..58cb9bf 100644 --- a/dither-image.js +++ b/dither-image.js @@ -5,18 +5,14 @@ import { import Jimp from 'Jimp'; -function repeat(val, n) { +function zeroes(n) { let arr = []; for (let i = 0; i < n; i++) { - arr.push(val); + arr.push(0); } return arr; } -function zeroes(n) { - return repeat(0, n); -} - function toLinear(val) { // use a 2.4 gamma approximation // this is BT.1886 compatible @@ -588,60 +584,29 @@ function decimate(input, palette, n) { // preface the reserved bits let buckets = reserved.slice().map((c) => [atariRGB[c]]).concat([input.slice()]); - if (input.length != 160) { - throw new Error('xxx bad input size'); - } - /* - let buckets = [input.slice()]; - if (reserved.length > 0) { - let pxPerReserved = input.length; - for (let c of reserved) { - for (let i = 0; i < pxPerReserved; i++) { - buckets[0].unshift(atariRGB[c]); - } - } - console.log(buckets[0].length, 'xxx'); - } - */ - - let magicSort = (picker) => (a, b) => { - let bychannel = picker(b) - picker(a); - if (bychannel) return bychannel; - - let byluma = b.luma() - a.luma(); - return byluma; - }; let medianCut = (bucket, range) => { if (bucket.length < 2) { + console.log(bucket); throw new Error('short bucket'); } //console.log('medianCut', bucket, range); // Sort by the channel with the greatest range, // then cut the bucket in two at the median. if (range.g >= range.r && range.g >= range.b) { - //bucket.sort((a, b) => b.g - a.g); - bucket.sort(magicSort((rgb) => rgb.g)); + bucket.sort((a, b) => b.g - a.g); } else if (range.r >= range.g && range.r >= range.b) { - //bucket.sort((a, b) => b.r - a.r); - bucket.sort(magicSort((rgb) => rgb.r)); + bucket.sort((a, b) => b.r - a.r); } else if (range.b >= range.g && range.b >= range.r) { - //bucket.sort((a, b) => b.b - a.b); - bucket.sort(magicSort((rgb) => rgb.b)); + bucket.sort((a, b) => b.b - a.b); } let half = bucket.length >> 1; //console.log('cutting', half, bucket.length); - let [bottom, top] = [bucket.slice(0, half), bucket.slice(half)]; - //console.log({bottom, top}); - return [bottom, top]; - //return [bucket.slice(0, half), bucket.slice(half)]; + return [bucket.slice(0, half), bucket.slice(half)]; }; while (buckets.length < n) { // Find the bucket with the greatest range in any channel let ranges = buckets.map((bucket) => { - if (bucket.length == 0) { - throw new Error('xxx empty bucket'); - } let red = bucket.map((rgb) => rgb.r); let green = bucket.map((rgb) => rgb.g); let blue = bucket.map((rgb) => rgb.b); @@ -657,26 +622,21 @@ function decimate(input, palette, n) { let greatest = 0; let index = -1; for (let i = 0; i < topRanges.length; i++) { - //if (topRanges[i] >= greatest) { - if (topRanges[i] > greatest) { + if (topRanges[i] >= greatest) { greatest = topRanges[i]; index = i; } } if (index == -1) { // We just ran out of colors! Pad the buckets. - //while (buckets.length < n) { - // buckets.push([new RGB(0, 0, 0)]); - //} + while (buckets.length < n) { + buckets.push([new RGB(0, 0, 0)]); + } break; } let [lo, hi] = medianCut(buckets[index], ranges[index]); buckets.splice(index, 1, lo, hi); } - - if (buckets.length > n) { - throw new Error('xxx too many colors assigned'); - } decimated = buckets.map((bucket) => { // Average the RGB colors in this chunk let rgb = bucket @@ -763,7 +723,10 @@ function decimate(input, palette, n) { let index = dists.indexOf(closest); return palette[index]; }); + // hack decimated.sort((a, b) => a - b); + //console.log(decimated); + decimated[0] = 0; // Palette fits return dither(decimated); @@ -779,25 +742,11 @@ async function loadImage(src) { let width = image.bitmap.width; let height = image.bitmap.height; - - let aspect = width / height; - let dar = 2 / 1.2; - if (aspect > ((320 / 1.2) / 192)) { - // wide + if (width != 160 || height != 160) { width = 160; - height = Math.round((width * image.bitmap.height / image.bitmap.width) * dar); - if (height & 1) { - height++; - } - } else { - // tall - height = 192; - width = Math.round((height * image.bitmap.width / image.bitmap.height) / dar); - if (width & 1) { - width++; - } + height = 160; + image = image.resize(width, height); } - image = image.resize(width, height); let rgba = image.bitmap.data.slice(); return { @@ -834,12 +783,13 @@ async function convert(source) { rgba } = await loadImage(source); - if (width > 160) { - throw new Error(`expected <160px width, got ${width} pixels`); + if (width !== 160) { + throw new Error(`expected 160px-compatible width, got ${width} pixels`); } - if (height > 192) { - throw new Error(`expected <192px height, got ${height} pixels`); + if (height !== 160) { + // @fixme support up to 240px + throw new Error(`expected 160px height, got ${height} pixels`); } if (rgba.length != width * 4 * height) { @@ -867,33 +817,19 @@ async function convert(source) { allColors.push(i); } - let left = [], right = []; - let padding = 0; - if (width < 160) { - padding = 160 - width; - - let black = new RGB(0, 0, 0); - left = repeat(black, padding >> 1); - right = repeat(black, padding + 1 >> 1); - } - let lines = []; for (let y = 0; y < height; y++) { let inputLine = input .slice(y * width, (y + 1) * width); - - if (padding) { - inputLine = left.concat(inputLine, right); - } if (y > 0) { let error = lines[y - 1].error; - inputLine = inputLine.map((rgb, x) => rgb.add(error[x]).clamp()); + inputLine = inputLine.map((rgb, x) => rgb.add(error[x])); } let line = decimate(inputLine, allColors, 4, y); lines.push(line); } return { - width: width + padding, + width, height, lines }; @@ -954,14 +890,12 @@ function genAssembly(width, height, nbits, lines) { return `.data .export frame1_top .export frame1_bottom - .export frame1_palette1_even .export frame1_palette1_odd .export frame1_palette2_even .export frame1_palette2_odd .export frame1_palette3_even .export frame1_palette3_odd - .export displaylist .segment "BUFFERS" @@ -970,10 +904,6 @@ function genAssembly(width, height, nbits, lines) { frame1_top: ${byte2byte(frame.bitmap.slice(0, half))} -.align 4096 -frame1_bottom: -${byte2byte(frame.bitmap.slice(half))} - .align 128 frame1_palette1_even: ${byte2byte(even(frame.palette1))} @@ -998,19 +928,22 @@ ${byte2byte(even(frame.palette3))} frame1_palette3_odd: ${byte2byte(odd(frame.palette3))} +.align 4096 +frame1_bottom: +${byte2byte(frame.bitmap.slice(half))} .align 1024 displaylist: - ; 24 lines overscan - .repeat 2 + ; 40 lines overscan + .repeat 4 .byte $70 ; 8 blank lines .endrep ; include a DLI to mark us as frame 0 .byte $f0 ; 8 blank lines - ; ${height} lines graphics + ; 160 lines graphics ; ANTIC mode e (160px 2bpp, 1 scan line per line) .byte $4e .addr frame1_top diff --git a/dither4.s b/dither4.s index fd4c0ec..9dfb994 100644 --- a/dither4.s +++ b/dither4.s @@ -28,13 +28,12 @@ sample_ptr = sample_ptrl scanline = $86 frame_counter = $89 -;height = 160 -height = 192 +height = 160 bytes_per_line = 40 pages_per_frame = 32 lines_per_frame = 262 ;scanline_offset = 31 + (40 - 24) / 2 -scanline_offset = 30 +scanline_offset = 46 scanline_max = (lines_per_frame - scanline_offset) / 2 .data diff --git a/video-bulk/atarifiy.sh b/video-bulk/atarifiy.sh deleted file mode 100644 index 95b8300..0000000 --- a/video-bulk/atarifiy.sh +++ /dev/null @@ -1,62 +0,0 @@ -set -e - -INFILE="$1" -# additional params can be input to the extraction -# for time or seek -shift - -mkdir -p temp - -ffmpeg \ - -i "$INFILE" \ - -r 60000/1001 \ - -vf 'scale=256:-2' \ - -an \ - "$@" \ - -y "temp/$INFILE-%04d.png" - -ffmpeg \ - -i "$INFILE" \ - -vn \ - -ac 1 \ - -ar 15734 \ - -acodec pcm_u8 \ - "$@" \ - -y "temp/$INFILE-audio.wav" || echo no audio - -for frame in "temp/$INFILE-"[0-9][0-9][0-9][0-9].png -do - n="${frame#temp/$INFILE-}" - n="${n%.png}" - out="temp/$INFILE-dither-${n}" - last="${n:0-1}" - node ../dither-image.js "$frame" "$out" & - if (( last == 9 )) - then - echo "frame $n" - wait - fi -done -wait - -if [ -f "temp/$INFILE-audio.wav" ] -then - ffmpeg \ - -r 60000/1001 \ - -i "temp/$INFILE-dither-%04d.png" \ - -i "temp/$INFILE-audio.wav" \ - -ac 2 \ - -ar 48000 \ - -vf 'pad=w=534' \ - -pix_fmt yuv420p \ - -movflags +faststart \ - -y "$INFILE-dither.mp4" -else - ffmpeg \ - -r 60000/1001 \ - -i "temp/$INFILE-dither-%04d.png" \ - -vf 'pad=w=534' \ - -pix_fmt yuv420p \ - -movflags +faststart \ - -y "$INFILE-dither.mp4" -fi diff --git a/video-cat/combine.sh b/video-cat/combine.sh deleted file mode 100644 index b3e81fb..0000000 --- a/video-cat/combine.sh +++ /dev/null @@ -1,10 +0,0 @@ -ffmpeg \ - -r 30000/1001 \ - -i 'frames/dither-%04d.png' \ - -i 'cats-audio.wav' \ - -ac 2 \ - -ar 48000 \ - -vf 'pad=w=534' \ - -pix_fmt yuv420p \ - -movflags +faststart \ - -y cats-dither.mp4 diff --git a/video-cat/extract.sh b/video-cat/extract.sh deleted file mode 100644 index b4faa3c..0000000 --- a/video-cat/extract.sh +++ /dev/null @@ -1,17 +0,0 @@ -set -a - -mkdir -p frames - -ffmpeg \ - -i 'cats computer fun.mp4' \ - -vf 'scale=256:144' \ - -an \ - -y 'frames/cats-%04d.png' - -ffmpeg \ - -i 'cats computer fun.mp4' \ - -vn \ - -ac 1 \ - -ar 15734 \ - -acodec pcm_u8 \ - -y 'cats-audio.wav' diff --git a/video-cat/video.sh b/video-cat/video.sh deleted file mode 100644 index 105fc48..0000000 --- a/video-cat/video.sh +++ /dev/null @@ -1,15 +0,0 @@ -set -e - -for frame in frames/cats-[0-9][0-9][0-9][0-9].png -do - n="${frame#frames/cats-}" - n="${n%.png}" - out="frames/dither-${n}" - last="${n:0-1}" - node ../dither-image.js "$frame" "$out" & - if (( last == 9 )) - then - wait - fi -done -wait diff --git a/video-doom/combine.sh b/video-doom/combine.sh deleted file mode 100644 index 45e2f68..0000000 --- a/video-doom/combine.sh +++ /dev/null @@ -1,10 +0,0 @@ -ffmpeg \ - -r 60000/1001 \ - -i 'frames/dither-%04d.png' \ - -i 'doom-audio.wav' \ - -ac 2 \ - -ar 48000 \ - -vf 'pad=w=534' \ - -pix_fmt yuv420p \ - -movflags +faststart \ - -y doom-dither.mp4 diff --git a/video-doom/extract.sh b/video-doom/extract.sh deleted file mode 100644 index 87d2972..0000000 --- a/video-doom/extract.sh +++ /dev/null @@ -1,22 +0,0 @@ -set -a - -mkdir -p frames - -TIME=37.5 - -ffmpeg \ - -i 'doom-speedrun.webm' \ - -t $TIME \ - -r 60000/1001 \ - -vf 'scale=256:192' \ - -an \ - -y 'frames/doom-%04d.png' - -ffmpeg \ - -i 'doom-speedrun.webm' \ - -t $TIME \ - -vn \ - -ac 1 \ - -ar 15734 \ - -acodec pcm_u8 \ - -y 'doom-audio.wav' diff --git a/video-doom/video.sh b/video-doom/video.sh deleted file mode 100644 index 3b5730e..0000000 --- a/video-doom/video.sh +++ /dev/null @@ -1,15 +0,0 @@ -set -e - -for frame in frames/doom-[0-9][0-9][0-9][0-9].png -do - n="${frame#frames/doom-}" - n="${n%.png}" - out="frames/dither-${n}" - last="${n:0-1}" - node ../dither-image.js "$frame" "$out" & - if (( last == 9 )) - then - wait - fi -done -wait diff --git a/video/combine.sh b/video/combine.sh index 38ba115..02c75ea 100644 --- a/video/combine.sh +++ b/video/combine.sh @@ -4,7 +4,7 @@ ffmpeg \ -i 'colamath-audio.wav' \ -ac 2 \ -ar 48000 \ - -vf 'pad=w=534' \ + -vf 'pad=w=640:h=360:x=52:y=20' \ -pix_fmt yuv420p \ -movflags +faststart \ -y colamath-dither.mp4 diff --git a/video/extract.sh b/video/extract.sh index 237613c..e626e3c 100644 --- a/video/extract.sh +++ b/video/extract.sh @@ -4,7 +4,7 @@ mkdir -p frames ffmpeg \ -i colamath-dv.avi \ - -vf 'yadif=1,scale=256:192' \ + -vf 'yadif=1,scale=160:200,crop=h=160' \ -an \ -y 'frames/colamath-%04d.png'