Как повысить скорость анимации живых обоев?
Я сделал для себя живые обои используя Node.js. В принципе, все работает, вот код:
const fileSystem = require("fs");
const wallpaper = require("wallpaper");
const { Canvas, loadImage } = require('canvas');
const canvas = new Canvas(1920, 1080);
const ctx = canvas.getContext('2d');
let pos = 0;
setInterval (() => {
ctx.fillStyle = "white";
ctx.fillRect(0, 0, 1920, 1080);
ctx.fillStyle = "black";
ctx.fillRect(pos, 200, 150, 150);
pos = (pos + 1) % 1920;
const buffer = canvas.toBuffer('image/png')
fileSystem.writeFileSync('./image.png', buffer);
(async () => {
await wallpaper.set("./image.png");
})();
}, 1000 / 60);
Но есть проблема - анимация идет со скоростью примерно в 10 fps. Кроме того, постоянное сохранение картинки и установка ее в качестве обоев загружают мне процессор на 70-80%, что не есть хорошо.
Как лучше реализовать эту идею?
Кстати, сейчас заметил, что в моем алгоритме есть еще одна проблема - если Node.js очень часто обращается к одному файлу, доступ к нему блокируется и выдается EBUSY Error. Так что, с этим тоже нужно что-то придумать.
Ответы (2 шт):
для начала попробуй использовать рекурсивный таймер.
https://learn.javascript.ru/settimeout-setinterval#rekursivnyy-settimeout
когда ты 60 раз в секунду переписываешь файл, он не успевает закрыться и может выдать ошибку EBUSY
А вообще как писали выше в комментариях к вопросу, лучше сделать это через gif анимацию
Как лучше реализовать эту идею?
Занятный вопрос. Конечно, вашу идею лучше реализовывать через любой инструмент анимации. Я в них не разбираюсь, так что в качестве примера приведу анимированный gif. Это - единственный способ минимизировать задержки анимации. Сгенерировать 100 фреймов, записать в файл и заняться генерацией следующего набора фреймов.
Но в комментариях вы настаивали на том, что вам нужна real-time генерация, так что давайте подумаем о том что можно улучшить.
Основная претензия к библиотеке canvas - она непозволительно медленная для вашей задачи. Генерация каждого фрейма на моём ноуте длится 150-170ms.
wallpaper-canvas: 170.962ms
wallpaper-canvas: 156.34ms
wallpaper-canvas: 157.196ms
wallpaper-canvas: 159.465ms
wallpaper-canvas: 154.248ms
wallpaper-canvas: 158.292ms
wallpaper-canvas: 164.503ms
wallpaper-canvas: 153.939ms
wallpaper-canvas: 164.663ms
wallpaper-canvas: 154.713ms
wallpaper-canvas: 163.566ms
wallpaper-canvas: 159.067ms
Нужен нормальный инструмент
Берём sharp.
Пишем примерно такую штуку:
- делаем холст размером с ваш квадратик и заливаем чёрным
- расширяем холст до размеров экрана и заливаем "расширитель" белым
- при выгрузке в png убираем компрессию до минимума, но так, чтобы экономия на компрессии не начала влиять на время записи на диск
- и ещё одна важная штука: если писать в файл так, как это делаете вы то будут несколько мгновений когда файл картинки будет неполным, не валидным. делаем запись атомарной, при генерации пишем во временный файл, а потом переименовываем временный файл в "основное" название.
Исходник
const path = require('path');
const fs = require('fs');
const sharp = require('sharp');
const truefile = path.join(__dirname, 'image.png');
const tmpfile = path.join(__dirname, 'image.png.tmp');
const screen = {
width: 1920,
height: 1080,
};
const rect = {
width: 150,
height: 150,
x: 0,
y: 200
};
let fn;
(fn = async () => {
rect.bb = {
top: rect.y,
bottom: screen.height - rect.height - rect.y,
left: rect.x,
right: screen.width - rect.width - rect.x
};
const buf = await makeFrame();
fs.writeFileSync(tmpfile, buf);
fs.renameSync(tmpfile, truefile);
rect.x = (rect.x + 1) % (screen.width - rect.width);
setTimeout(fn, 10);
})();
/**
* @returns {Promise<Buffer>}
*/
async function makeFrame() {
const image = sharp({
create: {
width: rect.width,
height: rect.height,
channels: 3,
background: {r: 0, g: 0, b: 0}
}
});
image.extend({
...rect.bb,
background: {r: 255, g: 255, b: 255}
});
image.png({
compressionLevel: 1
});
return image.toBuffer();
}
Результат работы
Среднее время генерации фрейма на моём ноуте равно 22ms. Ускорился почти на порядок!
А дальше можно хитрить: можно узнать у операционки разрешение экрана и генерировать картинку, например, в 1.5-2 раза меньшего размера. Пускать операционка натягивает её на экран, она это умеет.
Пропорционально уменьшив в 2 раза все атрибуты rect и rect.bb я получил время генерации фрейма равное 9-10ms, а это уже - очень хорошее время, прям real time.
Замечу, что вызов await wallpaper.set("./image.png") выполняется оочень долго. По сути, теперь именно он стал узким местом. Но, я уверен, можно найти быстрые низкоуровневые способы рассказать OS о том, что у неё новые обои. Я же исследовал только генерацию картинок.
