Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions common/icons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ import IconPrinter from "tabler_icons_tsx/tsx/printer.tsx";
import IconSettings from "tabler_icons_tsx/tsx/settings.tsx";
import IconMinus from "tabler_icons_tsx/tsx/minus.tsx";
import IconClipboardPlus from "tabler_icons_tsx/tsx/clipboard-plus.tsx";
import IconGridDots from "tabler_icons_tsx/tsx/grid-dots.tsx";
import IconBrush from "tabler_icons_tsx/tsx/brush.tsx";
import IconCircles from "tabler_icons_tsx/tsx/circles.tsx";
import IconGrain from "tabler_icons_tsx/tsx/grain.tsx";
import { INL_ICON_COLOR, INL_ICON_SIZE } from "./constants.ts";

export const Icons = {
Expand Down Expand Up @@ -59,6 +63,10 @@ export const Icons = {
IconSettings,
IconMinus,
IconClipboardPlus,
IconGridDots,
IconBrush,
IconCircles,
IconGrain,
};

for (const key in Icons) {
Expand Down
4 changes: 2 additions & 2 deletions common/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export interface StuffData {
/* TODO: better way than such a trigger? */
triggerPaste?: boolean;

dither?: 'pic' | 'pattern' | 'text';
dither?: 'steinberg' | 'bayer' | 'atkinson' | 'pattern' | 'text';
rotate?: 0 | 90 | 180 | 270;
flipH?: boolean;
flipV?: boolean;
Expand Down Expand Up @@ -68,7 +68,7 @@ export interface StuffPainterProps {

export interface ImageWorkerMessage {
id: number;
dither: 'pic' | 'pattern' | 'text';
dither: 'steinberg' | 'bayer' | 'atkinson' | 'pattern' | 'text';
rotate: 0 | 90 | 180 | 270;
flip: 'none' | 'h' | 'v' | 'both';
brightness: number;
Expand Down
32 changes: 25 additions & 7 deletions components/StuffWidget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ export default function StuffWidget(props: StuffProps) {
if (blob === null) return;
if (!['pic'].includes(stuff.type)) {
stuff.type = 'pic';
stuff.dither = 'pic';
stuff.dither = 'steinberg';
}
const url = URL.createObjectURL(blob);
use_pic(url).then(() => URL.revokeObjectURL(url));
Expand All @@ -122,7 +122,7 @@ export default function StuffWidget(props: StuffProps) {
stuff.dither = 'text';
break;
case 'pic':
stuff.dither = 'pic';
stuff.dither = 'steinberg';
break;
}
dispatch({ action: 'modify', stuff: stuff });
Expand Down Expand Up @@ -232,12 +232,30 @@ export default function StuffWidget(props: StuffProps) {
</>;
options = <>
<div class="stuff__option">
<span class="option__title">{_('process-as')}</span>
<button class="option__item" value="pic"
<span class="option__title">{_('dither')}</span>
<button class="option__item" value="steinberg"
onClick={mkmodify('dither')}
data-selected={stuff.dither === 'pic'}>
<Icons.IconPhoto />
<span class="stuff__label">{_('picture')}</span>
data-selected={stuff.dither === 'steinberg'}>
<Icons.IconGrain />
<span class="stuff__label">{_('steinberg')}</span>
</button>
<button class="option__item" value="bayer"
onClick={mkmodify('dither')}
data-selected={stuff.dither === 'bayer'}>
<Icons.IconGridDots />
<span class="stuff__label">{_('bayer')}</span>
</button>
<button class="option__item" value="atkinson"
onClick={mkmodify('dither')}
data-selected={stuff.dither === 'atkinson'}>
<Icons.IconBrush />
<span class="stuff__label">{_('atkinson')}</span>
</button>
<button class="option__item" value="pattern"
onClick={mkmodify('dither')}
data-selected={stuff.dither === 'pattern'}>
<Icons.IconCircles />
<span class="stuff__label">{_('pattern')}</span>
</button>
<button class="option__item" value="text"
onClick={mkmodify('dither')}
Expand Down
2 changes: 1 addition & 1 deletion islands/KittyPrinter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ function properStuff(stuff: StuffData) {
break;
case 'pic':
stuff = Object.assign({
dither: 'pic',
dither: 'steinberg',
rotate: 0,
flipH: false,
flipV: false,
Expand Down
74 changes: 73 additions & 1 deletion static/image_worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,72 @@ function ditherHalftone(mono, w, h) {
return mono;
}

// Looked at https://github.com/Lana-chan/webgbcam for the
// Bayer/ordered dithering function.
function ditherBayer(mono, w, h) {
const bayer8 = [
0, 48, 12, 60, 3, 51, 15, 63,
32, 16, 44, 28, 35, 19, 47, 31,
8, 56, 4, 52, 11, 59, 7, 55,
40, 24, 36, 20, 43, 27, 39, 23,
2, 50, 14, 62, 1, 49, 13, 61,
34, 18, 46, 30, 33, 17, 45, 29,
10, 58, 6, 54, 9, 57, 5, 53,
42, 26, 38, 22, 41, 25, 37, 21
];

const ditherFactor = 0.6; // Same as webgbcam default
let p = 0;

for (let j = 0; j < h; ++j) {
for (let i = 0; i < w; ++i) {
let bayerValue = bayer8[(j % 8) * 8 + (i % 8)];

let pixelValue = mono[p];
pixelValue = pixelValue + ((bayerValue - 32) * ditherFactor);

if (pixelValue < 0) pixelValue = 0;
if (pixelValue > 255) pixelValue = 255;

mono[p] = pixelValue > 128 ? 0xff : 0x00;
++p;
}
}
return mono;
}

// Thanks Bill! https://beyondloom.com/blog/dither.html
function ditherAtkinson(mono, w, h) {
let p = 0;
let oldPixel, newPixel, error;

for (let j = 0; j < h; ++j) {
for (let i = 0; i < w; ++i) {
oldPixel = mono[p];
newPixel = oldPixel > 0x80 ? 0xff : 0x00;
error = (oldPixel - newPixel) >> 3; // Divide by 8
mono[p] = newPixel;

if (i < w - 1)
mono[p + 1] += error;
if (i < w - 2)
mono[p + 2] += error;
if (j < h - 1) {
if (i > 0)
mono[p + w - 1] += error;
mono[p + w] += error;
if (i < w - 1)
mono[p + w + 1] += error;
}
if (j < h - 2)
mono[p + 2 * w] += error;

++p;
}
}
return mono;
}

function rotate(before, w, h, turn) {
const after = new Uint8Array(before.length);
switch (turn) {
Expand Down Expand Up @@ -147,9 +213,15 @@ self.addEventListener('message', function(event) {
const w = msg.width, h = msg.height;
let mono = rgbaToGray(input, msg.brightness, true);
switch (msg.dither) {
case 'pic':
case 'steinberg':
mono = ditherSteinberg(mono, w, h);
break;
case 'bayer':
mono = ditherBayer(mono, w, h);
break;
case 'atkinson':
mono = ditherAtkinson(mono, w, h);
break;
case 'pattern':
mono = ditherHalftone(mono, w, h);
break;
Expand Down
6 changes: 5 additions & 1 deletion static/lang/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,18 @@
"add": "Add",
"text": "Text",
"picture": "Picture",
"bayer": "Bayer",
"atkinson": "Atkinson",
"pattern": "Pattern",
"remove": "Remove",
"hello-world": "Hello, world!",
"align": "Align",
"left": "Left",
"center": "Center",
"right": "Right",
"options": "Options",
"process-as": "Process as",
"dither": "Dither",
"steinberg": "Steinberg",
"font-size": "Font size",
"line-spacing": "Line Spacing",
"justify": "Justify",
Expand Down
Loading