Add Image processor example

This commit is contained in:
pengfei.zhou 2021-11-23 11:52:38 +08:00 committed by osborn
parent ca73b594e4
commit eb72ddad4f
2 changed files with 405 additions and 290 deletions

View File

@ -12,246 +12,24 @@ import {
createRef,
loge,
} from "doric";
import {
binarization,
extractGrayValue,
fastBlur,
gaussianBlur,
ostu,
vampix,
} from "./imageUtils";
import { colors } from "./utils";
function fastBlur(
pixels: Uint32Array | Array<number>,
w: number,
h: number,
radius: number
) {
const wm = w - 1;
const hm = h - 1;
const wh = w * h;
const div = 2 * radius + 1;
const r: number[] = new Array(wh);
const g: number[] = new Array(wh);
const b: number[] = new Array(wh);
let rsum = 0,
gsum = 0,
bsum = 0,
x,
y,
i,
p,
yp,
yi,
yw;
const vmin: number[] = new Array(Math.max(w, h));
let divsum = (div + 1) >> 1;
divsum *= divsum;
const dv: number[] = new Array(256 * divsum);
for (i = 0; i < 256 * divsum; i++) {
dv[i] = Math.round(i / divsum);
}
yw = yi = 0;
const stack = new Array(div).fill(0).map((_) => new Array(3));
let stackpointer;
let stackstart;
let sir: number[];
let rbs;
let r1 = radius + 1;
let routsum, goutsum, boutsum;
let rinsum, ginsum, binsum;
for (y = 0; y < h; y++) {
rinsum =
ginsum =
binsum =
routsum =
goutsum =
boutsum =
rsum =
gsum =
bsum =
0;
for (i = -radius; i <= radius; i++) {
p = pixels[yi + Math.min(wm, Math.max(i, 0))];
sir = stack[i + radius];
sir[0] = p & 0xff;
sir[1] = (p >> 8) & 0xff;
sir[2] = (p >> 16) & 0xff;
rbs = r1 - Math.abs(i);
rsum += sir[0] * rbs;
gsum += sir[1] * rbs;
bsum += sir[2] * rbs;
if (i > 0) {
rinsum += sir[0];
ginsum += sir[1];
binsum += sir[2];
} else {
routsum += sir[0];
goutsum += sir[1];
boutsum += sir[2];
}
}
stackpointer = radius;
for (x = 0; x < w; x++) {
r[yi] = dv[rsum];
g[yi] = dv[gsum];
b[yi] = dv[bsum];
rsum -= routsum;
gsum -= goutsum;
bsum -= boutsum;
stackstart = stackpointer - radius + div;
sir = stack[stackstart % div];
routsum -= sir[0];
goutsum -= sir[1];
boutsum -= sir[2];
if (y == 0) {
vmin[x] = Math.min(x + radius + 1, wm);
}
p = pixels[yw + vmin[x]];
sir[0] = p & 0xff;
sir[1] = (p >> 8) & 0xff;
sir[2] = (p >> 16) & 0xff;
rinsum += sir[0];
ginsum += sir[1];
binsum += sir[2];
rsum += rinsum;
gsum += ginsum;
bsum += binsum;
stackpointer = (stackpointer + 1) % div;
sir = stack[stackpointer % div];
routsum += sir[0];
goutsum += sir[1];
boutsum += sir[2];
rinsum -= sir[0];
ginsum -= sir[1];
binsum -= sir[2];
yi++;
}
yw += w;
}
for (x = 0; x < w; x++) {
rinsum =
ginsum =
binsum =
routsum =
goutsum =
boutsum =
rsum =
gsum =
bsum =
0;
yp = -radius * w;
for (i = -radius; i <= radius; i++) {
yi = Math.max(0, yp) + x;
sir = stack[i + radius];
sir[0] = r[yi];
sir[1] = g[yi];
sir[2] = b[yi];
rbs = r1 - Math.abs(i);
rsum += r[yi] * rbs;
gsum += g[yi] * rbs;
bsum += b[yi] * rbs;
if (i > 0) {
rinsum += sir[0];
ginsum += sir[1];
binsum += sir[2];
} else {
routsum += sir[0];
goutsum += sir[1];
boutsum += sir[2];
}
if (i < hm) {
yp += w;
}
}
yi = x;
stackpointer = radius;
for (y = 0; y < h; y++) {
pixels[yi] =
dv[rsum] |
((dv[gsum] & 0xff) << 8) |
((dv[bsum] & 0xff) << 16) |
(pixels[yi] & 0xff000000);
rsum -= routsum;
gsum -= goutsum;
bsum -= boutsum;
stackstart = stackpointer - radius + div;
sir = stack[stackstart % div];
routsum -= sir[0];
goutsum -= sir[1];
boutsum -= sir[2];
if (x == 0) {
vmin[y] = Math.min(y + r1, hm) * w;
}
p = x + vmin[y];
sir[0] = r[p];
sir[1] = g[p];
sir[2] = b[p];
rinsum += sir[0];
ginsum += sir[1];
binsum += sir[2];
rsum += rinsum;
gsum += ginsum;
bsum += binsum;
stackpointer = (stackpointer + 1) % div;
sir = stack[stackpointer];
routsum += sir[0];
goutsum += sir[1];
boutsum += sir[2];
rinsum -= sir[0];
ginsum -= sir[1];
binsum -= sir[2];
yi += w;
}
}
}
function pixelToRGBA(pixel: number) {
const r = pixel & 0xff;
const g = (pixel >> 8) & 0xff;
const b = (pixel >> 16) & 0xff;
const a = (pixel >> 24) & 0xff;
return { r, g, b, a };
}
function rgbaToPixel(rgba: { r: number; g: number; b: number; a: number }) {
return (
(rgba.r & 0xff) +
((rgba.g & 0xff) << 8) +
((rgba.b & 0xff) << 16) +
((rgba.a & 0xff) << 24)
);
}
@Entry
export class ImageProcessorDemo extends Panel {
build(root: Group): void {
const iv = createRef<Image>();
const imageUrl = "https://doric.pub/about/The_Parthenon_in_Athens.jpg"; //"https://doric.pub/logo.png";
// const imageUrl = "https://doric.pub/about/The_Parthenon_in_Athens.jpg";
const imageUrl =
"https://img-blog.csdn.net/20181022194542112?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzA0NjY1Mw==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70";
//"https://doric.pub/logo.png";
<Scroller parent={root} layoutConfig={layoutConfig().most()}>
<VLayout
layoutConfig={layoutConfig().mostWidth().fitHeight()}
@ -277,16 +55,43 @@ export class ImageProcessorDemo extends Panel {
{(
[
[
"黑白",
"黑白",
async () => {
const imageInfo = await iv.current.getImageInfo(context);
const pixels = await iv.current.getImagePixels(context);
const data = new Uint32Array(pixels);
for (let i = 0; i < data.length; i++) {
let { r, g, b, a } = pixelToRGBA(data[i]);
r = g = b = Math.floor(r * 0.2989 + g * 0.587 + b * 0.114);
data[i] = rgbaToPixel({ r, g, b, a });
}
vampix(data);
iv.current.imagePixels = {
width: imageInfo.width,
height: imageInfo.height,
pixels: pixels,
};
},
],
[
"二值化",
async () => {
const imageInfo = await iv.current.getImageInfo(context);
const pixels = await iv.current.getImagePixels(context);
const data = new Uint32Array(pixels);
extractGrayValue(data);
binarization(data, 127);
iv.current.imagePixels = {
width: imageInfo.width,
height: imageInfo.height,
pixels: pixels,
};
},
],
[
"OSTU",
async () => {
const imageInfo = await iv.current.getImageInfo(context);
const pixels = await iv.current.getImagePixels(context);
const data = new Uint32Array(pixels);
extractGrayValue(data);
const t = ostu(data);
binarization(data, t);
iv.current.imagePixels = {
width: imageInfo.width,
height: imageInfo.height,
@ -312,58 +117,9 @@ export class ImageProcessorDemo extends Panel {
"高斯模糊",
async () => {
const imageInfo = await iv.current.getImageInfo(context);
loge(imageInfo);
const pixels = await iv.current.getImagePixels(context);
const data = new Uint32Array(pixels);
function getPixel(x: number, y: number) {
return data[
Math.max(0, Math.min(x, imageInfo.width - 1)) +
Math.max(0, Math.min(y, imageInfo.height - 1)) *
imageInfo.width
];
}
function gaussian(x: number, y: number) {
const q = 1.5;
return (
(1 / (2 * Math.PI * q * q)) *
Math.exp(-(x * x + y * y) / (2 * q * q))
);
}
const radius = 1;
const weights: number[][] = new Array(radius * 2 + 1)
.fill(0)
.map((_) => new Array(radius * 2 + 1).fill(0));
let allWeight = 0;
for (let x = 0; x < radius * 2 + 1; x++) {
for (let y = 0; y < radius * 2 + 1; y++) {
weights[x][y] = gaussian(x - radius, y - radius);
allWeight += weights[x][y];
}
}
for (let x = 0; x < radius * 2 + 1; x++) {
for (let y = 0; y < radius * 2 + 1; y++) {
weights[x][y] = weights[x][y] / allWeight;
}
}
for (let j = 0; j < imageInfo.height; j++) {
for (let i = 0; i < imageInfo.width; i++) {
const rgba = { r: 0, g: 0, b: 0, a: 0 };
for (let lx = 0; lx < radius * 2 + 1; lx++) {
for (let ly = 0; ly < radius * 2 + 1; ly++) {
const { r, g, b, a } = pixelToRGBA(
getPixel(lx - radius + i, ly - radius + j)
);
rgba.r += r * weights[lx][ly];
rgba.g += g * weights[lx][ly];
rgba.b += b * weights[lx][ly];
rgba.a += a * weights[lx][ly];
}
}
data[i + j * imageInfo.width] = rgbaToPixel(rgba);
}
}
gaussianBlur(data, imageInfo.width, imageInfo.height, 1);
iv.current.imagePixels = {
width: imageInfo.width,
height: imageInfo.height,

View File

@ -0,0 +1,359 @@
export function pixelToRGBA(pixel: number) {
const r = pixel & 0xff;
const g = (pixel >> 8) & 0xff;
const b = (pixel >> 16) & 0xff;
const a = (pixel >> 24) & 0xff;
return { r, g, b, a };
}
export function rgbaToPixel(rgba: { r: number; g: number; b: number; a: number }) {
return (
(rgba.r & 0xff) +
((rgba.g & 0xff) << 8) +
((rgba.b & 0xff) << 16) +
((rgba.a & 0xff) << 24)
);
}
export function extractGrayValue(pixels: Uint32Array) {
for (let i = 0; i < pixels.length; i++) {
let { r, g, b } = pixelToRGBA(pixels[i]);
pixels[i] = Math.floor(r * 0.2989 + g * 0.587 + b * 0.114);
}
return pixels
}
export function ostu(grayData: Uint32Array | Array<number>) {
let threshold = 1;
let start = 0,
duration = 0xff;
const records: Record<number, number> = {};
function compute(t: number) {
if (records[t] != undefined) {
return records[t];
}
let u0 = 0;
let u1 = 0;
let n0: number[] = [];
let n1: number[] = [];
for (let e of grayData) {
if (e < t) {
u0 += e;
n0.push(e);
} else {
u1 += e;
n1.push(e);
}
}
const w0 = n0.length;
const w1 = n1.length;
u0 /= w0;
u1 /= w1;
const ret = w0 * w1 * (u0 - u1) * (u0 - u1);
records[t] = ret;
return ret;
}
while (duration >= 2) {
threshold = start + Math.floor(duration / 2);
const g = compute(threshold);
const g1 = compute(threshold - 1);
if (g1 < g) {
const g2 = compute(threshold + 1);
if (g < g2) {
duration = start + duration - threshold;
start = threshold;
} else {
break;
}
} else {
duration = threshold - start;
}
}
return threshold;
}
export function vampix(pixels: Uint32Array | Array<number>) {
for (let i = 0; i < pixels.length; i++) {
let { r, g, b, a } = pixelToRGBA(pixels[i]);
r = g = b = Math.floor(r * 0.2989 + g * 0.587 + b * 0.114);
pixels[i] = rgbaToPixel({ r, g, b, a });
}
}
export function binarization(pixels: Uint32Array | Array<number>, threshold: number) {
for (let i = 0; i < pixels.length; i++) {
pixels[i] = pixels[i] < threshold ? 0xff000000 : 0xffffffff;
}
}
export function fastBlur(
pixels: Uint32Array | Array<number>,
w: number,
h: number,
radius: number
) {
const wm = w - 1;
const hm = h - 1;
const wh = w * h;
const div = 2 * radius + 1;
const r: number[] = new Array(wh);
const g: number[] = new Array(wh);
const b: number[] = new Array(wh);
let rsum = 0,
gsum = 0,
bsum = 0,
x,
y,
i,
p,
yp,
yi,
yw;
const vmin: number[] = new Array(Math.max(w, h));
let divsum = (div + 1) >> 1;
divsum *= divsum;
const dv: number[] = new Array(256 * divsum);
for (i = 0; i < 256 * divsum; i++) {
dv[i] = Math.round(i / divsum);
}
yw = yi = 0;
const stack = new Array(div).fill(0).map((_) => new Array(3));
let stackpointer;
let stackstart;
let sir: number[];
let rbs;
let r1 = radius + 1;
let routsum, goutsum, boutsum;
let rinsum, ginsum, binsum;
for (y = 0; y < h; y++) {
rinsum =
ginsum =
binsum =
routsum =
goutsum =
boutsum =
rsum =
gsum =
bsum =
0;
for (i = -radius; i <= radius; i++) {
p = pixels[yi + Math.min(wm, Math.max(i, 0))];
sir = stack[i + radius];
sir[0] = p & 0xff;
sir[1] = (p >> 8) & 0xff;
sir[2] = (p >> 16) & 0xff;
rbs = r1 - Math.abs(i);
rsum += sir[0] * rbs;
gsum += sir[1] * rbs;
bsum += sir[2] * rbs;
if (i > 0) {
rinsum += sir[0];
ginsum += sir[1];
binsum += sir[2];
} else {
routsum += sir[0];
goutsum += sir[1];
boutsum += sir[2];
}
}
stackpointer = radius;
for (x = 0; x < w; x++) {
r[yi] = dv[rsum];
g[yi] = dv[gsum];
b[yi] = dv[bsum];
rsum -= routsum;
gsum -= goutsum;
bsum -= boutsum;
stackstart = stackpointer - radius + div;
sir = stack[stackstart % div];
routsum -= sir[0];
goutsum -= sir[1];
boutsum -= sir[2];
if (y == 0) {
vmin[x] = Math.min(x + radius + 1, wm);
}
p = pixels[yw + vmin[x]];
sir[0] = p & 0xff;
sir[1] = (p >> 8) & 0xff;
sir[2] = (p >> 16) & 0xff;
rinsum += sir[0];
ginsum += sir[1];
binsum += sir[2];
rsum += rinsum;
gsum += ginsum;
bsum += binsum;
stackpointer = (stackpointer + 1) % div;
sir = stack[stackpointer % div];
routsum += sir[0];
goutsum += sir[1];
boutsum += sir[2];
rinsum -= sir[0];
ginsum -= sir[1];
binsum -= sir[2];
yi++;
}
yw += w;
}
for (x = 0; x < w; x++) {
rinsum =
ginsum =
binsum =
routsum =
goutsum =
boutsum =
rsum =
gsum =
bsum =
0;
yp = -radius * w;
for (i = -radius; i <= radius; i++) {
yi = Math.max(0, yp) + x;
sir = stack[i + radius];
sir[0] = r[yi];
sir[1] = g[yi];
sir[2] = b[yi];
rbs = r1 - Math.abs(i);
rsum += r[yi] * rbs;
gsum += g[yi] * rbs;
bsum += b[yi] * rbs;
if (i > 0) {
rinsum += sir[0];
ginsum += sir[1];
binsum += sir[2];
} else {
routsum += sir[0];
goutsum += sir[1];
boutsum += sir[2];
}
if (i < hm) {
yp += w;
}
}
yi = x;
stackpointer = radius;
for (y = 0; y < h; y++) {
pixels[yi] =
dv[rsum] |
((dv[gsum] & 0xff) << 8) |
((dv[bsum] & 0xff) << 16) |
(pixels[yi] & 0xff000000);
rsum -= routsum;
gsum -= goutsum;
bsum -= boutsum;
stackstart = stackpointer - radius + div;
sir = stack[stackstart % div];
routsum -= sir[0];
goutsum -= sir[1];
boutsum -= sir[2];
if (x == 0) {
vmin[y] = Math.min(y + r1, hm) * w;
}
p = x + vmin[y];
sir[0] = r[p];
sir[1] = g[p];
sir[2] = b[p];
rinsum += sir[0];
ginsum += sir[1];
binsum += sir[2];
rsum += rinsum;
gsum += ginsum;
bsum += binsum;
stackpointer = (stackpointer + 1) % div;
sir = stack[stackpointer];
routsum += sir[0];
goutsum += sir[1];
boutsum += sir[2];
rinsum -= sir[0];
ginsum -= sir[1];
binsum -= sir[2];
yi += w;
}
}
}
export function gaussianBlur(pixels: Uint32Array | Array<number>,
w: number,
h: number,
radius: number) {
function getPixel(x: number, y: number) {
return pixels[
Math.max(0, Math.min(x, w - 1)) +
Math.max(0, Math.min(y, h - 1)) *
w
];
}
function gaussian(x: number, y: number) {
const q = 1.5;
return (
(1 / (2 * Math.PI * q * q)) *
Math.exp(-(x * x + y * y) / (2 * q * q))
);
}
const weights: number[][] = new Array(radius * 2 + 1)
.fill(0)
.map((_) => new Array(radius * 2 + 1).fill(0));
let allWeight = 0;
for (let x = 0; x < radius * 2 + 1; x++) {
for (let y = 0; y < radius * 2 + 1; y++) {
weights[x][y] = gaussian(x - radius, y - radius);
allWeight += weights[x][y];
}
}
for (let x = 0; x < radius * 2 + 1; x++) {
for (let y = 0; y < radius * 2 + 1; y++) {
weights[x][y] = weights[x][y] / allWeight;
}
}
for (let j = 0; j < h; j++) {
for (let i = 0; i < w; i++) {
const rgba = { r: 0, g: 0, b: 0, a: 0 };
for (let lx = 0; lx < radius * 2 + 1; lx++) {
for (let ly = 0; ly < radius * 2 + 1; ly++) {
const { r, g, b, a } = pixelToRGBA(
getPixel(lx - radius + i, ly - radius + j)
);
rgba.r += r * weights[lx][ly];
rgba.g += g * weights[lx][ly];
rgba.b += b * weights[lx][ly];
rgba.a += a * weights[lx][ly];
}
}
pixels[i + j * w] = rgbaToPixel(rgba);
}
}
}