update samples
|
@ -9,7 +9,7 @@
|
|||
|
||||
## Changelog
|
||||
|
||||
### **HEAD -> main** 2022/09/14 mandic00@live.com
|
||||
### **HEAD -> main** 2022/09/17 mandic00@live.com
|
||||
|
||||
|
||||
### **2.10.2** 2022/09/11 mandic00@live.com
|
||||
|
|
2
TODO.md
|
@ -48,3 +48,5 @@ Enable via `about:config` -> `gfx.offscreencanvas.enabled`
|
|||
- New simple demo [*Live*](https://vladmandic.github.io/human/demo/video/index.html)
|
||||
- Enable model cache when using web workers
|
||||
- Improve NodeJS resolver when using ESM
|
||||
- Update demo `demo/nodejs/process-folder.js`
|
||||
and re-process `/samples`
|
||||
|
|
|
@ -13,7 +13,7 @@ const childProcess = require('child_process'); // eslint-disable-line camelcase
|
|||
// note that main process does not import human or tfjs at all, it's all done from worker process
|
||||
|
||||
const workerFile = 'demo/multithread/node-multiprocess-worker.js';
|
||||
const imgPathRoot = './assets'; // modify to include your sample images
|
||||
const imgPathRoot = './samples/in'; // modify to include your sample images
|
||||
const numWorkers = 4; // how many workers will be started
|
||||
const workers = []; // this holds worker processes
|
||||
const images = []; // this holds queue of enumerated images
|
||||
|
@ -22,7 +22,7 @@ let numImages;
|
|||
|
||||
// trigered by main when worker sends ready message
|
||||
// if image pool is empty, signal worker to exit otherwise dispatch image to worker and remove image from queue
|
||||
async function detect(worker) {
|
||||
async function submitDetect(worker) {
|
||||
if (!t[2]) t[2] = process.hrtime.bigint(); // first time do a timestamp so we can measure initial latency
|
||||
if (images.length === numImages) worker.send({ test: true }); // for first image in queue just measure latency
|
||||
if (images.length === 0) worker.send({ exit: true }); // nothing left in queue
|
||||
|
@ -79,7 +79,7 @@ async function main() {
|
|||
// if message is processing result, just print how many faces were detected
|
||||
// otherwise it's an unknown message
|
||||
workers[i].on('message', (msg) => {
|
||||
if (msg.ready) detect(workers[i]);
|
||||
if (msg.ready) submitDetect(workers[i]);
|
||||
else if (msg.image) log.data('Main: worker finished:', workers[i].pid, 'detected faces:', msg.detected.face?.length, 'bodies:', msg.detected.body?.length, 'hands:', msg.detected.hand?.length, 'objects:', msg.detected.object?.length);
|
||||
else if (msg.test) measureLatency();
|
||||
else log.data('Main: worker message:', workers[i].pid, msg);
|
||||
|
|
|
@ -15,8 +15,8 @@ const Human = require('../../dist/human.node.js'); // use this when using human
|
|||
|
||||
const config = { // just enable all and leave default settings
|
||||
debug: false,
|
||||
face: { enabled: true }, // includes mesh, iris, emotion, descriptor
|
||||
hand: { enabled: true, maxDetected: 2, minConfidence: 0.5, detector: { modelPath: 'handtrack.json' } }, // use alternative hand model
|
||||
face: { enabled: true, detector: { maxDetected: 10 } }, // includes mesh, iris, emotion, descriptor
|
||||
hand: { enabled: true, maxDetected: 20, minConfidence: 0.5, detector: { modelPath: 'handtrack.json' } }, // use alternative hand model
|
||||
body: { enabled: true },
|
||||
object: { enabled: true },
|
||||
gestures: { enabled: true },
|
||||
|
|
|
@ -12,6 +12,7 @@ const Human = require('../../dist/human.node.js'); // use this when using human
|
|||
|
||||
const humanConfig = {
|
||||
// add any custom config here
|
||||
debug: true,
|
||||
};
|
||||
|
||||
async function detect(inputFile) {
|
||||
|
|
|
@ -13,54 +13,52 @@ const process = require('process');
|
|||
const log = require('@vladmandic/pilogger'); // eslint-disable-line node/no-unpublished-require
|
||||
const canvas = require('canvas'); // eslint-disable-line node/no-unpublished-require
|
||||
// for nodejs, `tfjs-node` or `tfjs-node-gpu` should be loaded before using Human
|
||||
const tf = require('@tensorflow/tfjs-node'); // eslint-disable-line node/no-unpublished-require
|
||||
const tf = require('@tensorflow/tfjs-node-gpu'); // eslint-disable-line node/no-unpublished-require
|
||||
const Human = require('../../dist/human.node-gpu.js'); // this is 'const Human = require('../dist/human.node-gpu.js').default;'
|
||||
|
||||
const config = { // just enable all and leave default settings
|
||||
modelBasePath: 'file://models',
|
||||
debug: true,
|
||||
async: false,
|
||||
softwareKernels: true, // slower but enhanced precision since face rotation can work in software mode in nodejs environments
|
||||
cacheSensitivity: 0,
|
||||
face: { enabled: true, detector: { maxDetected: 20 } },
|
||||
object: { enabled: true },
|
||||
face: { enabled: true, detector: { maxDetected: 100, minConfidence: 0.1 } },
|
||||
object: { enabled: true, maxDetected: 100, minConfidence: 0.1 },
|
||||
gesture: { enabled: true },
|
||||
hand: { enabled: true },
|
||||
body: { enabled: true, modelPath: 'https://vladmandic.github.io/human-models/models/movenet-multipose.json' },
|
||||
hand: { enabled: true, maxDetected: 100, minConfidence: 0.2 },
|
||||
body: { enabled: true, maxDetected: 100, minConfidence: 0.1, modelPath: 'https://vladmandic.github.io/human-models/models/movenet-multipose.json' },
|
||||
};
|
||||
|
||||
async function main() {
|
||||
log.header();
|
||||
const poolSize = 4;
|
||||
|
||||
globalThis.Canvas = canvas.Canvas; // patch global namespace with canvas library
|
||||
globalThis.ImageData = canvas.ImageData; // patch global namespace with canvas library
|
||||
const human = new Human.Human(config); // create instance of human
|
||||
|
||||
const human = new Human.Human(config); // create instance of human
|
||||
log.info('Human:', human.version, 'TF:', tf.version_core);
|
||||
const configErrors = await human.validate();
|
||||
if (configErrors.length > 0) log.error('Configuration errors:', configErrors);
|
||||
await human.load(); // pre-load models
|
||||
log.info('Loaded models:', Object.keys(human.models).filter((a) => human.models[a]));
|
||||
async function saveFile(shape, buffer, result, outFile) {
|
||||
return new Promise(async (resolve, reject) => { // eslint-disable-line no-async-promise-executor
|
||||
const outputCanvas = new canvas.Canvas(shape[2], shape[1]); // create canvas
|
||||
const outputCtx = outputCanvas.getContext('2d');
|
||||
const inputImage = await canvas.loadImage(buffer); // load image using canvas library
|
||||
outputCtx.drawImage(inputImage, 0, 0); // draw input image onto canvas
|
||||
human.draw.all(outputCanvas, result); // use human build-in method to draw results as overlays on canvas
|
||||
const outStream = fs.createWriteStream(outFile); // write canvas to new image file
|
||||
outStream.on('finish', () => {
|
||||
log.data('Output image:', outFile, outputCanvas.width, outputCanvas.height);
|
||||
resolve();
|
||||
});
|
||||
outStream.on('error', (err) => {
|
||||
log.error('Output error:', outFile, err);
|
||||
reject();
|
||||
});
|
||||
const stream = outputCanvas.createJPEGStream({ quality: 0.5, progressive: true, chromaSubsampling: true });
|
||||
stream.pipe(outStream);
|
||||
});
|
||||
}
|
||||
|
||||
const inDir = process.argv[2];
|
||||
const outDir = process.argv[3];
|
||||
if (process.argv.length !== 4) {
|
||||
log.error('Parameters: <input-directory> <output-directory> missing');
|
||||
return;
|
||||
}
|
||||
if (!fs.existsSync(inDir) || !fs.statSync(inDir).isDirectory() || !fs.existsSync(outDir) || !fs.statSync(outDir).isDirectory()) {
|
||||
log.error('Invalid directory specified:', 'input:', fs.existsSync(inDir) ?? fs.statSync(inDir).isDirectory(), 'output:', fs.existsSync(outDir) ?? fs.statSync(outDir).isDirectory());
|
||||
return;
|
||||
}
|
||||
|
||||
const dir = fs.readdirSync(inDir);
|
||||
const images = dir.filter((f) => fs.statSync(path.join(inDir, f)).isFile() && (f.toLocaleLowerCase().endsWith('.jpg') || f.toLocaleLowerCase().endsWith('.jpeg')));
|
||||
log.info(`Processing folder: ${inDir} entries:`, dir.length, 'images', images.length);
|
||||
for (const image of images) {
|
||||
const inFile = path.join(inDir, image);
|
||||
async function processFile(image, inFile, outFile) {
|
||||
const buffer = fs.readFileSync(inFile);
|
||||
const tensor = human.tf.tidy(() => {
|
||||
const decode = human.tf.node.decodeImage(buffer, 3);
|
||||
const expand = human.tf.expandDims(decode, 0);
|
||||
const cast = human.tf.cast(expand, 'float32');
|
||||
const tensor = tf.tidy(() => {
|
||||
const decode = tf.node.decodeImage(buffer, 3);
|
||||
const expand = tf.expandDims(decode, 0);
|
||||
const cast = tf.cast(expand, 'float32');
|
||||
return cast;
|
||||
});
|
||||
log.state('Loaded image:', inFile, tensor.shape);
|
||||
|
@ -69,18 +67,53 @@ async function main() {
|
|||
human.tf.dispose(tensor);
|
||||
log.data(`Detected: ${image}:`, 'Face:', result.face.length, 'Body:', result.body.length, 'Hand:', result.hand.length, 'Objects:', result.object.length, 'Gestures:', result.gesture.length);
|
||||
|
||||
const outputCanvas = new canvas.Canvas(tensor.shape[2], tensor.shape[1]); // create canvas
|
||||
const outputCtx = outputCanvas.getContext('2d');
|
||||
const inputImage = await canvas.loadImage(buffer); // load image using canvas library
|
||||
outputCtx.drawImage(inputImage, 0, 0); // draw input image onto canvas
|
||||
human.draw.all(outputCanvas, result); // use human build-in method to draw results as overlays on canvas
|
||||
const outFile = path.join(outDir, image);
|
||||
const outStream = fs.createWriteStream(outFile); // write canvas to new image file
|
||||
outStream.on('finish', () => log.state('Output image:', outFile, outputCanvas.width, outputCanvas.height));
|
||||
outStream.on('error', (err) => log.error('Output error:', outFile, err));
|
||||
const stream = outputCanvas.createJPEGStream({ quality: 0.5, progressive: true, chromaSubsampling: true });
|
||||
stream.pipe(outStream);
|
||||
if (outFile) await saveFile(tensor.shape, buffer, result, outFile);
|
||||
}
|
||||
|
||||
async function main() {
|
||||
log.header();
|
||||
|
||||
globalThis.Canvas = canvas.Canvas; // patch global namespace with canvas library
|
||||
globalThis.ImageData = canvas.ImageData; // patch global namespace with canvas library
|
||||
|
||||
log.info('Human:', human.version, 'TF:', tf.version_core);
|
||||
const configErrors = await human.validate();
|
||||
if (configErrors.length > 0) log.error('Configuration errors:', configErrors);
|
||||
await human.load(); // pre-load models
|
||||
log.info('Loaded models:', Object.keys(human.models).filter((a) => human.models[a]));
|
||||
|
||||
const inDir = process.argv[2];
|
||||
const outDir = process.argv[3];
|
||||
if (!inDir) {
|
||||
log.error('Parameters: <input-directory> missing');
|
||||
return;
|
||||
}
|
||||
if (inDir && (!fs.existsSync(inDir) || !fs.statSync(inDir).isDirectory())) {
|
||||
log.error('Invalid input directory:', fs.existsSync(inDir) ?? fs.statSync(inDir).isDirectory());
|
||||
return;
|
||||
}
|
||||
if (!outDir) {
|
||||
log.info('Parameters: <output-directory> missing, images will not be saved');
|
||||
}
|
||||
if (outDir && (!fs.existsSync(outDir) || !fs.statSync(outDir).isDirectory())) {
|
||||
log.error('Invalid output directory:', fs.existsSync(outDir) ?? fs.statSync(outDir).isDirectory());
|
||||
return;
|
||||
}
|
||||
|
||||
const dir = fs.readdirSync(inDir);
|
||||
const images = dir.filter((f) => fs.statSync(path.join(inDir, f)).isFile() && (f.toLocaleLowerCase().endsWith('.jpg') || f.toLocaleLowerCase().endsWith('.jpeg')));
|
||||
log.info(`Processing folder: ${inDir} entries:`, dir.length, 'images', images.length);
|
||||
const t0 = performance.now();
|
||||
const promises = [];
|
||||
for (let i = 0; i < images.length; i++) {
|
||||
const inFile = path.join(inDir, images[i]);
|
||||
const outFile = outDir ? path.join(outDir, images[i]) : null;
|
||||
promises.push(processFile(images[i], inFile, outFile));
|
||||
if (i % poolSize === 0) await Promise.all(promises);
|
||||
}
|
||||
await Promise.all(promises);
|
||||
const t1 = performance.now();
|
||||
log.info(`Processed ${images.length} images in ${Math.round(t1 - t0)} ms`);
|
||||
}
|
||||
|
||||
main();
|
||||
|
|
|
@ -85192,7 +85192,7 @@ function setModelLoadOptions(config3) {
|
|||
options2.modelBasePath = config3.modelBasePath;
|
||||
}
|
||||
async function loadModel(modelPath) {
|
||||
var _a, _b, _c;
|
||||
var _a, _b, _c, _d;
|
||||
let modelUrl = join(options2.modelBasePath, modelPath || "");
|
||||
if (!modelUrl.toLowerCase().endsWith(".json"))
|
||||
modelUrl += ".json";
|
||||
|
@ -85215,16 +85215,23 @@ async function loadModel(modelPath) {
|
|||
}
|
||||
modelStats[shortModelName].inCache = options2.cacheSupported && options2.cacheModels && Object.keys(cachedModels).includes(cachedModelName);
|
||||
const tfLoadOptions = typeof fetch === "undefined" ? {} : { fetchFunc: (url, init3) => httpHandler(url, init3) };
|
||||
const model20 = new GraphModel(modelStats[shortModelName].inCache ? cachedModelName : modelUrl, tfLoadOptions);
|
||||
let model20 = new GraphModel(modelStats[shortModelName].inCache ? cachedModelName : modelUrl, tfLoadOptions);
|
||||
let loaded = false;
|
||||
try {
|
||||
model20.findIOHandler();
|
||||
if (options2.debug)
|
||||
log("model load handler:", model20["handler"]);
|
||||
const artifacts = await model20.handler.load();
|
||||
modelStats[shortModelName].sizeFromManifest = ((_a = artifacts == null ? void 0 : artifacts.weightData) == null ? void 0 : _a.byteLength) || 0;
|
||||
} catch (err) {
|
||||
log("error finding model i/o handler:", modelUrl, err);
|
||||
}
|
||||
try {
|
||||
const artifacts = await ((_a = model20.handler) == null ? void 0 : _a.load()) || null;
|
||||
modelStats[shortModelName].sizeFromManifest = ((_b = artifacts == null ? void 0 : artifacts.weightData) == null ? void 0 : _b.byteLength) || 0;
|
||||
if (artifacts)
|
||||
model20.loadSync(artifacts);
|
||||
modelStats[shortModelName].sizeLoadedWeights = ((_c = (_b = model20.artifacts) == null ? void 0 : _b.weightData) == null ? void 0 : _c.byteLength) || 0;
|
||||
else
|
||||
model20 = await loadGraphModel(modelStats[shortModelName].inCache ? cachedModelName : modelUrl, tfLoadOptions);
|
||||
modelStats[shortModelName].sizeLoadedWeights = ((_d = (_c = model20.artifacts) == null ? void 0 : _c.weightData) == null ? void 0 : _d.byteLength) || 0;
|
||||
if (options2.verbose)
|
||||
log("load:", { model: shortModelName, url: model20["modelUrl"], bytes: modelStats[shortModelName].sizeLoadedWeights });
|
||||
loaded = true;
|
||||
|
|
|
@ -90,7 +90,7 @@
|
|||
"@vladmandic/tfjs": "github:vladmandic/tfjs",
|
||||
"@webgpu/types": "^0.1.21",
|
||||
"canvas": "^2.10.1",
|
||||
"esbuild": "^0.15.7",
|
||||
"esbuild": "^0.15.8",
|
||||
"eslint": "8.23.1",
|
||||
"eslint-config-airbnb-base": "^15.0.0",
|
||||
"eslint-plugin-html": "^7.1.0",
|
||||
|
@ -102,7 +102,7 @@
|
|||
"rimraf": "^3.0.2",
|
||||
"seedrandom": "^3.0.5",
|
||||
"tslib": "^2.4.0",
|
||||
"typedoc": "0.23.14",
|
||||
"typedoc": "0.23.15",
|
||||
"typescript": "4.8.3"
|
||||
}
|
||||
}
|
||||
|
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 43 KiB |
Before Width: | Height: | Size: 77 KiB After Width: | Height: | Size: 90 KiB |
Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 78 KiB |
Before Width: | Height: | Size: 105 KiB After Width: | Height: | Size: 106 KiB |
Before Width: | Height: | Size: 88 KiB After Width: | Height: | Size: 99 KiB |
Before Width: | Height: | Size: 178 KiB After Width: | Height: | Size: 188 KiB |
Before Width: | Height: | Size: 170 KiB After Width: | Height: | Size: 174 KiB |
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 59 KiB |
Before Width: | Height: | Size: 104 KiB After Width: | Height: | Size: 117 KiB |
Before Width: | Height: | Size: 69 KiB After Width: | Height: | Size: 77 KiB |
Before Width: | Height: | Size: 98 KiB After Width: | Height: | Size: 99 KiB |
Before Width: | Height: | Size: 102 KiB After Width: | Height: | Size: 105 KiB |
Before Width: | Height: | Size: 100 KiB After Width: | Height: | Size: 108 KiB |
Before Width: | Height: | Size: 73 KiB After Width: | Height: | Size: 80 KiB |
Before Width: | Height: | Size: 74 KiB After Width: | Height: | Size: 74 KiB |
Before Width: | Height: | Size: 74 KiB After Width: | Height: | Size: 75 KiB |
Before Width: | Height: | Size: 79 KiB After Width: | Height: | Size: 81 KiB |
Before Width: | Height: | Size: 83 KiB After Width: | Height: | Size: 86 KiB |
Before Width: | Height: | Size: 59 KiB After Width: | Height: | Size: 58 KiB |
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 66 KiB |
Before Width: | Height: | Size: 102 KiB After Width: | Height: | Size: 116 KiB |
Before Width: | Height: | Size: 81 KiB After Width: | Height: | Size: 81 KiB |
Before Width: | Height: | Size: 111 KiB After Width: | Height: | Size: 114 KiB |
Before Width: | Height: | Size: 98 KiB After Width: | Height: | Size: 115 KiB |
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 51 KiB |
Before Width: | Height: | Size: 80 KiB After Width: | Height: | Size: 91 KiB |
Before Width: | Height: | Size: 136 KiB After Width: | Height: | Size: 162 KiB |
Before Width: | Height: | Size: 104 KiB After Width: | Height: | Size: 108 KiB |
Before Width: | Height: | Size: 100 KiB After Width: | Height: | Size: 108 KiB |
Before Width: | Height: | Size: 88 KiB After Width: | Height: | Size: 90 KiB |
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 65 KiB |
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 68 KiB |
Before Width: | Height: | Size: 83 KiB After Width: | Height: | Size: 91 KiB |
Before Width: | Height: | Size: 100 KiB After Width: | Height: | Size: 100 KiB |
Before Width: | Height: | Size: 84 KiB After Width: | Height: | Size: 97 KiB |
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 106 KiB |
Before Width: | Height: | Size: 100 KiB After Width: | Height: | Size: 119 KiB |
Before Width: | Height: | Size: 70 KiB After Width: | Height: | Size: 90 KiB |
Before Width: | Height: | Size: 70 KiB After Width: | Height: | Size: 90 KiB |
Before Width: | Height: | Size: 71 KiB After Width: | Height: | Size: 88 KiB |
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 98 KiB |
Before Width: | Height: | Size: 105 KiB After Width: | Height: | Size: 113 KiB |
Before Width: | Height: | Size: 346 KiB After Width: | Height: | Size: 399 KiB |
Before Width: | Height: | Size: 130 KiB After Width: | Height: | Size: 143 KiB |
Before Width: | Height: | Size: 310 KiB After Width: | Height: | Size: 294 KiB |
Before Width: | Height: | Size: 287 KiB After Width: | Height: | Size: 352 KiB |
Before Width: | Height: | Size: 400 KiB After Width: | Height: | Size: 432 KiB |
Before Width: | Height: | Size: 223 KiB After Width: | Height: | Size: 232 KiB |
Before Width: | Height: | Size: 220 KiB After Width: | Height: | Size: 238 KiB |
Before Width: | Height: | Size: 75 KiB After Width: | Height: | Size: 73 KiB |
Before Width: | Height: | Size: 75 KiB After Width: | Height: | Size: 84 KiB |
Before Width: | Height: | Size: 132 KiB After Width: | Height: | Size: 145 KiB |
Before Width: | Height: | Size: 103 KiB After Width: | Height: | Size: 113 KiB |
Before Width: | Height: | Size: 116 KiB After Width: | Height: | Size: 128 KiB |
Before Width: | Height: | Size: 198 KiB After Width: | Height: | Size: 204 KiB |
Before Width: | Height: | Size: 77 KiB After Width: | Height: | Size: 81 KiB |
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 56 KiB |
Before Width: | Height: | Size: 79 KiB After Width: | Height: | Size: 86 KiB |
Before Width: | Height: | Size: 121 KiB After Width: | Height: | Size: 120 KiB |
Before Width: | Height: | Size: 197 KiB After Width: | Height: | Size: 200 KiB |
Before Width: | Height: | Size: 254 KiB After Width: | Height: | Size: 322 KiB |
Before Width: | Height: | Size: 80 KiB After Width: | Height: | Size: 92 KiB |
Before Width: | Height: | Size: 98 KiB After Width: | Height: | Size: 105 KiB |
Before Width: | Height: | Size: 94 KiB After Width: | Height: | Size: 102 KiB |
Before Width: | Height: | Size: 105 KiB After Width: | Height: | Size: 109 KiB |
Before Width: | Height: | Size: 112 KiB After Width: | Height: | Size: 113 KiB |
Before Width: | Height: | Size: 104 KiB After Width: | Height: | Size: 109 KiB |
Before Width: | Height: | Size: 95 KiB After Width: | Height: | Size: 100 KiB |
Before Width: | Height: | Size: 92 KiB After Width: | Height: | Size: 96 KiB |
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 61 KiB |
Before Width: | Height: | Size: 57 KiB After Width: | Height: | Size: 64 KiB |
Before Width: | Height: | Size: 71 KiB After Width: | Height: | Size: 67 KiB |
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 58 KiB |
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 62 KiB |
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 70 KiB |
Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 56 KiB |
Before Width: | Height: | Size: 57 KiB After Width: | Height: | Size: 59 KiB |
Before Width: | Height: | Size: 188 KiB After Width: | Height: | Size: 210 KiB |
Before Width: | Height: | Size: 116 KiB After Width: | Height: | Size: 136 KiB |
Before Width: | Height: | Size: 74 KiB After Width: | Height: | Size: 78 KiB |
Before Width: | Height: | Size: 123 KiB After Width: | Height: | Size: 121 KiB |
Before Width: | Height: | Size: 90 KiB After Width: | Height: | Size: 95 KiB |
Before Width: | Height: | Size: 134 KiB After Width: | Height: | Size: 135 KiB |
Before Width: | Height: | Size: 140 KiB After Width: | Height: | Size: 144 KiB |