Skip to content

Commit 3016189

Browse files
authored
Merge pull request #20 from pjaspers/main
Adding more dithering options
2 parents ed9027b + fa22ed4 commit 3016189

File tree

6 files changed

+114
-12
lines changed

6 files changed

+114
-12
lines changed

common/icons.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ import IconPrinter from "tabler_icons_tsx/tsx/printer.tsx";
2727
import IconSettings from "tabler_icons_tsx/tsx/settings.tsx";
2828
import IconMinus from "tabler_icons_tsx/tsx/minus.tsx";
2929
import IconClipboardPlus from "tabler_icons_tsx/tsx/clipboard-plus.tsx";
30+
import IconGridDots from "tabler_icons_tsx/tsx/grid-dots.tsx";
31+
import IconBrush from "tabler_icons_tsx/tsx/brush.tsx";
32+
import IconCircles from "tabler_icons_tsx/tsx/circles.tsx";
33+
import IconGrain from "tabler_icons_tsx/tsx/grain.tsx";
3034
import { INL_ICON_COLOR, INL_ICON_SIZE } from "./constants.ts";
3135

3236
export const Icons = {
@@ -59,6 +63,10 @@ export const Icons = {
5963
IconSettings,
6064
IconMinus,
6165
IconClipboardPlus,
66+
IconGridDots,
67+
IconBrush,
68+
IconCircles,
69+
IconGrain,
6270
};
6371

6472
for (const key in Icons) {

common/types.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ export interface StuffData {
1515
/* TODO: better way than such a trigger? */
1616
triggerPaste?: boolean;
1717

18-
dither?: 'pic' | 'pattern' | 'text';
18+
dither?: 'steinberg' | 'bayer' | 'atkinson' | 'pattern' | 'text';
1919
rotate?: 0 | 90 | 180 | 270;
2020
flipH?: boolean;
2121
flipV?: boolean;
@@ -68,7 +68,7 @@ export interface StuffPainterProps {
6868

6969
export interface ImageWorkerMessage {
7070
id: number;
71-
dither: 'pic' | 'pattern' | 'text';
71+
dither: 'steinberg' | 'bayer' | 'atkinson' | 'pattern' | 'text';
7272
rotate: 0 | 90 | 180 | 270;
7373
flip: 'none' | 'h' | 'v' | 'both';
7474
brightness: number;

components/StuffWidget.tsx

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ export default function StuffWidget(props: StuffProps) {
105105
if (blob === null) return;
106106
if (!['pic'].includes(stuff.type)) {
107107
stuff.type = 'pic';
108-
stuff.dither = 'pic';
108+
stuff.dither = 'steinberg';
109109
}
110110
const url = URL.createObjectURL(blob);
111111
use_pic(url).then(() => URL.revokeObjectURL(url));
@@ -122,7 +122,7 @@ export default function StuffWidget(props: StuffProps) {
122122
stuff.dither = 'text';
123123
break;
124124
case 'pic':
125-
stuff.dither = 'pic';
125+
stuff.dither = 'steinberg';
126126
break;
127127
}
128128
dispatch({ action: 'modify', stuff: stuff });
@@ -232,12 +232,30 @@ export default function StuffWidget(props: StuffProps) {
232232
</>;
233233
options = <>
234234
<div class="stuff__option">
235-
<span class="option__title">{_('process-as')}</span>
236-
<button class="option__item" value="pic"
235+
<span class="option__title">{_('dither')}</span>
236+
<button class="option__item" value="steinberg"
237237
onClick={mkmodify('dither')}
238-
data-selected={stuff.dither === 'pic'}>
239-
<Icons.IconPhoto />
240-
<span class="stuff__label">{_('picture')}</span>
238+
data-selected={stuff.dither === 'steinberg'}>
239+
<Icons.IconGrain />
240+
<span class="stuff__label">{_('steinberg')}</span>
241+
</button>
242+
<button class="option__item" value="bayer"
243+
onClick={mkmodify('dither')}
244+
data-selected={stuff.dither === 'bayer'}>
245+
<Icons.IconGridDots />
246+
<span class="stuff__label">{_('bayer')}</span>
247+
</button>
248+
<button class="option__item" value="atkinson"
249+
onClick={mkmodify('dither')}
250+
data-selected={stuff.dither === 'atkinson'}>
251+
<Icons.IconBrush />
252+
<span class="stuff__label">{_('atkinson')}</span>
253+
</button>
254+
<button class="option__item" value="pattern"
255+
onClick={mkmodify('dither')}
256+
data-selected={stuff.dither === 'pattern'}>
257+
<Icons.IconCircles />
258+
<span class="stuff__label">{_('pattern')}</span>
241259
</button>
242260
<button class="option__item" value="text"
243261
onClick={mkmodify('dither')}

islands/KittyPrinter.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ function properStuff(stuff: StuffData) {
3131
break;
3232
case 'pic':
3333
stuff = Object.assign({
34-
dither: 'pic',
34+
dither: 'steinberg',
3535
rotate: 0,
3636
flipH: false,
3737
flipV: false,

static/image_worker.js

Lines changed: 73 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,72 @@ function ditherHalftone(mono, w, h) {
8181
return mono;
8282
}
8383

84+
// Looked at https://github.com/Lana-chan/webgbcam for the
85+
// Bayer/ordered dithering function.
86+
function ditherBayer(mono, w, h) {
87+
const bayer8 = [
88+
0, 48, 12, 60, 3, 51, 15, 63,
89+
32, 16, 44, 28, 35, 19, 47, 31,
90+
8, 56, 4, 52, 11, 59, 7, 55,
91+
40, 24, 36, 20, 43, 27, 39, 23,
92+
2, 50, 14, 62, 1, 49, 13, 61,
93+
34, 18, 46, 30, 33, 17, 45, 29,
94+
10, 58, 6, 54, 9, 57, 5, 53,
95+
42, 26, 38, 22, 41, 25, 37, 21
96+
];
97+
98+
const ditherFactor = 0.6; // Same as webgbcam default
99+
let p = 0;
100+
101+
for (let j = 0; j < h; ++j) {
102+
for (let i = 0; i < w; ++i) {
103+
let bayerValue = bayer8[(j % 8) * 8 + (i % 8)];
104+
105+
let pixelValue = mono[p];
106+
pixelValue = pixelValue + ((bayerValue - 32) * ditherFactor);
107+
108+
if (pixelValue < 0) pixelValue = 0;
109+
if (pixelValue > 255) pixelValue = 255;
110+
111+
mono[p] = pixelValue > 128 ? 0xff : 0x00;
112+
++p;
113+
}
114+
}
115+
return mono;
116+
}
117+
118+
// Thanks Bill! https://beyondloom.com/blog/dither.html
119+
function ditherAtkinson(mono, w, h) {
120+
let p = 0;
121+
let oldPixel, newPixel, error;
122+
123+
for (let j = 0; j < h; ++j) {
124+
for (let i = 0; i < w; ++i) {
125+
oldPixel = mono[p];
126+
newPixel = oldPixel > 0x80 ? 0xff : 0x00;
127+
error = (oldPixel - newPixel) >> 3; // Divide by 8
128+
mono[p] = newPixel;
129+
130+
if (i < w - 1)
131+
mono[p + 1] += error;
132+
if (i < w - 2)
133+
mono[p + 2] += error;
134+
if (j < h - 1) {
135+
if (i > 0)
136+
mono[p + w - 1] += error;
137+
mono[p + w] += error;
138+
if (i < w - 1)
139+
mono[p + w + 1] += error;
140+
}
141+
if (j < h - 2)
142+
mono[p + 2 * w] += error;
143+
144+
++p;
145+
}
146+
}
147+
return mono;
148+
}
149+
84150
function rotate(before, w, h, turn) {
85151
const after = new Uint8Array(before.length);
86152
switch (turn) {
@@ -147,9 +213,15 @@ self.addEventListener('message', function(event) {
147213
const w = msg.width, h = msg.height;
148214
let mono = rgbaToGray(input, msg.brightness, true);
149215
switch (msg.dither) {
150-
case 'pic':
216+
case 'steinberg':
151217
mono = ditherSteinberg(mono, w, h);
152218
break;
219+
case 'bayer':
220+
mono = ditherBayer(mono, w, h);
221+
break;
222+
case 'atkinson':
223+
mono = ditherAtkinson(mono, w, h);
224+
break;
153225
case 'pattern':
154226
mono = ditherHalftone(mono, w, h);
155227
break;

static/lang/en-US.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,18 @@
44
"add": "Add",
55
"text": "Text",
66
"picture": "Picture",
7+
"bayer": "Bayer",
8+
"atkinson": "Atkinson",
9+
"pattern": "Pattern",
710
"remove": "Remove",
811
"hello-world": "Hello, world!",
912
"align": "Align",
1013
"left": "Left",
1114
"center": "Center",
1215
"right": "Right",
1316
"options": "Options",
14-
"process-as": "Process as",
17+
"dither": "Dither",
18+
"steinberg": "Steinberg",
1519
"font-size": "Font size",
1620
"line-spacing": "Line Spacing",
1721
"justify": "Justify",

0 commit comments

Comments
 (0)