Skip to content

Commit

Permalink
feat: replace sharp with skia-canvas for placeholder drawing
Browse files Browse the repository at this point in the history
  • Loading branch information
Julusian committed Jul 16, 2023
1 parent 103344d commit 3c01b55
Show file tree
Hide file tree
Showing 11 changed files with 219 additions and 272 deletions.
Binary file modified assets/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
25 changes: 2 additions & 23 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,6 @@
"prepare": "husky install",
"dev": "yarn ts-node src/main.ts",
"dev-electron": "yarn build:main && electron dist/electron.js",
"dist:prepare:sharp": "cd node_modules/sharp && rimraf vendor && node install/libvips && node install/dll-copy",
"electron-rebuild": "yarn dist:prepare:sharp",
"build": "rimraf dist && yarn build:main",
"build:main": "tsc -p tsconfig.build.json",
"lint:raw": "eslint --ext .ts --ext .js --ext .tsx --ext .jsx --ignore-pattern dist",
Expand All @@ -37,7 +35,6 @@
"@types/electron-prompt": "^1.6.1",
"@types/node": "^18.16.19",
"@types/node-hid": "^1.3.1",
"@types/sharp": "^0.32.0",
"cross-env": "^7.0.3",
"electron": "^25.3.0",
"electron-builder": "^24.4.0",
Expand All @@ -53,6 +50,7 @@
"@elgato-stream-deck/node": "^5.7.3",
"@julusian/image-rs": "^0.1.3",
"@julusian/jpeg-turbo": "^2.1.0",
"@julusian/skia-canvas": "^1.0.4",
"@loupedeck/node": "^0.4.0",
"@xencelabs-quick-keys/node": "^0.4.0",
"electron-about-window": "^1.15.2",
Expand All @@ -64,7 +62,6 @@
"meow": "^9.0.0",
"node-hid": "npm:@julusian/hid@2.5.0-3",
"semver": "^7.5.4",
"sharp": "^0.32.1",
"tslib": "^2.6.0",
"usb": "^2.9.0"
},
Expand Down Expand Up @@ -96,15 +93,6 @@
"LSBackgroundOnly": 1,
"LSUIElement": 1
},
"extraFiles": [
{
"from": "./node_modules/sharp/vendor/${env.VIPS_VENDOR}/lib",
"to": "Frameworks",
"filter": [
"libvips*.dylib"
]
}
],
"hardenedRuntime": "true",
"gatekeeperAssess": "false",
"entitlements": "entitlements.mac.plist",
Expand All @@ -126,16 +114,7 @@
},
"linux": {
"target": "tar.gz",
"artifactName": "companion-satellite-${arch}.tar.gz",
"extraFiles": [
{
"from": "./node_modules/sharp/vendor/${env.VIPS_VENDOR}/lib",
"to": ".",
"filter": [
"libvips*.so.*"
]
}
]
"artifactName": "companion-satellite-${arch}.tar.gz"
},
"files": [
"**/*",
Expand Down
75 changes: 41 additions & 34 deletions src/cards.ts
Original file line number Diff line number Diff line change
@@ -1,52 +1,59 @@
import * as path from 'path'
import { promisify } from 'util'
import { readFile } from 'fs'
import * as sharp from 'sharp'
import { Canvas, Image, loadImage } from '@julusian/skia-canvas'

const readFileP = promisify(readFile)

export class CardGenerator {
private iconImage: Buffer | undefined
private iconImage: Image | undefined

async loadIcon(): Promise<Buffer> {
constructor() {
// Ensure skia-canvas is loaded at startup
new Canvas()
}

async loadIcon(): Promise<Image> {
if (!this.iconImage) {
const rawData = await readFileP(path.join(__dirname, '../assets/icon.png'))
this.iconImage = rawData

this.iconImage = await loadImage(rawData)
}

return this.iconImage
}

async generateBasicCard(width: number, height: number, remoteIp: string, status: string): Promise<Buffer> {
const size = Math.round(Math.min(width, height) * 0.6)
const icon = await sharp(await this.loadIcon())
.resize(size)
.toBuffer()

return sharp({
create: {
width: width,
height: height,
channels: 3,
background: { r: 0, g: 0, b: 0 },
},
})
.composite([
{
input: icon,
},
{
input: Buffer.from(
`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${width - 20} 40" version="1.1">
<text font-family="'sans-serif'" font-size="12px" x="10" y="32" fill="#fff" text-anchor="left">Remote: ${remoteIp}</text>
<text font-family="'sans-serif'" font-size="12px" x="10" y="12" fill="#fff" text-anchor="left">Status: ${status}</text>
</svg>`
),
top: height - 20,
left: 10,
},
])
.removeAlpha()
.toBuffer()
const iconImage = await this.loadIcon()

const canvas = new Canvas(width, height)
const context2d = canvas.getContext('2d')

// draw icon
const iconTargetSize = Math.round(Math.min(width, height) * 0.6)
const iconTargetX = (width - iconTargetSize) / 2
const iconTargetY = (height - iconTargetSize) / 2
context2d.drawImage(
iconImage,
0,
0,
iconImage.width,
iconImage.height,
iconTargetX,
iconTargetY,
iconTargetSize,
iconTargetSize
)

// draw text
context2d.font = `normal normal normal ${12}px sans-serif`
context2d.textAlign = 'left'
context2d.fillStyle = '#ffffff'

context2d.fillText(`Remote: ${remoteIp}`, 10, height - 10)
context2d.fillText(`Status: ${status}`, 10, height - 30)

// return result
return Buffer.from(context2d.getImageData(0, 0, width, height).data)
}
}
3 changes: 3 additions & 0 deletions src/device-types/infinitton.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { CompanionSatelliteClient } from '../client'
import { CardGenerator } from '../cards'
import { DeviceDrawProps, DeviceRegisterProps, WrappedDevice } from './api'
import Infinitton = require('infinitton-idisplay')
import { rgbaToRgb } from '../lib'

export class InfinittonWrapper implements WrappedDevice {
readonly #cardGenerator: CardGenerator
Expand Down Expand Up @@ -71,6 +72,8 @@ export class InfinittonWrapper implements WrappedDevice {
this.#cardGenerator
.generateBasicCard(width, height, hostname, status)
.then(async (buffer) => {
buffer = await rgbaToRgb(buffer, width, height)

if (status === this.#currentStatus) {
// still valid
this.#panel.fillPanelImage(buffer)
Expand Down
3 changes: 3 additions & 0 deletions src/device-types/loupedeck-live-s.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { CompanionSatelliteClient } from '../client'
import { CardGenerator } from '../cards'
import { ImageWriteQueue } from '../writeQueue'
import { DeviceDrawProps, DeviceRegisterProps, WrappedDevice } from './api'
import { rgbaToRgb } from '../lib'

export class LoupedeckLiveSWrapper implements WrappedDevice {
readonly #cardGenerator: CardGenerator
Expand Down Expand Up @@ -228,6 +229,8 @@ export class LoupedeckLiveSWrapper implements WrappedDevice {
this.#cardGenerator
.generateBasicCard(width, height, hostname, status)
.then(async (buffer) => {
buffer = await rgbaToRgb(buffer, width, height)

if (outputId === this.#queueOutputId) {
console.log('draw buffer')
this.#isShowingCard = true
Expand Down
3 changes: 3 additions & 0 deletions src/device-types/loupedeck-live.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { CompanionSatelliteClient } from '../client'
import { CardGenerator } from '../cards'
import { ImageWriteQueue } from '../writeQueue'
import { DeviceDrawProps, DeviceRegisterProps, WrappedDevice } from './api'
import { rgbaToRgb } from '../lib'

export class LoupedeckLiveWrapper implements WrappedDevice {
readonly #cardGenerator: CardGenerator
Expand Down Expand Up @@ -233,6 +234,8 @@ export class LoupedeckLiveWrapper implements WrappedDevice {
this.#cardGenerator
.generateBasicCard(width, height, hostname, status)
.then(async (buffer) => {
buffer = await rgbaToRgb(buffer, width, height)

if (outputId === this.#queueOutputId) {
this.#isShowingCard = true
// still valid
Expand Down
3 changes: 3 additions & 0 deletions src/device-types/razer-stream-controller-x.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { CompanionSatelliteClient } from '../client'
import { CardGenerator } from '../cards'
import { ImageWriteQueue } from '../writeQueue'
import { DeviceDrawProps, DeviceRegisterProps, WrappedDevice } from './api'
import { rgbaToRgb } from '../lib'

export class RazerStreamControllerXWrapper implements WrappedDevice {
readonly #cardGenerator: CardGenerator
Expand Down Expand Up @@ -137,6 +138,8 @@ export class RazerStreamControllerXWrapper implements WrappedDevice {
this.#cardGenerator
.generateBasicCard(width, height, hostname, status)
.then(async (buffer) => {
buffer = await rgbaToRgb(buffer, width, height)

if (outputId === this.#queueOutputId) {
console.log('draw buffer')
this.#isShowingCard = true
Expand Down
4 changes: 3 additions & 1 deletion src/device-types/streamdeck.ts
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,9 @@ export class StreamDeckWrapper implements WrappedDevice {
.then(async (buffer) => {
if (outputId === this.#queueOutputId) {
// still valid
await this.#deck.fillPanelBuffer(buffer)
await this.#deck.fillPanelBuffer(buffer, {
format: 'rgba',
})
}
})
.catch((e) => {
Expand Down
10 changes: 10 additions & 0 deletions src/lib.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
import * as imageRs from '@julusian/image-rs'

export const DEFAULT_PORT = 16622

export function assertNever(_v: never): void {
// Nothing to do
}

export async function rgbaToRgb(input: Uint8Array, width: number, height: number): Promise<Buffer> {
return Buffer.from(
(await imageRs.ImageTransformer.fromBuffer(input, width, height, imageRs.PixelFormat.Rgba)
.scale(width, height)
.toBuffer(imageRs.PixelFormat.Rgb)) as Uint8Array
)
}
34 changes: 0 additions & 34 deletions tools/build_electron.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@ function $withoutEscaping(pieces, ...args) {
const platform = argv._[0]

let electronBuilderArgs = []
let sharpPlatform = null
let sharpArch = null

if (!platform) {
console.log('No platform specified, building for current')
Expand All @@ -23,52 +21,20 @@ if (!platform) {

if (platform === 'mac-x64') {
electronBuilderArgs.push('--x64', '--mac')
sharpPlatform = 'darwin'
sharpArch = 'x64'
} else if (platform === 'mac-arm64') {
electronBuilderArgs.push('--arm64', '--mac')
sharpPlatform = 'darwin'
sharpArch = 'arm64'
} else if (platform === 'win-x64') {
electronBuilderArgs.push('--x64', '--win')
sharpPlatform = 'win32'
sharpArch = 'x64'
} else if (platform === 'linux-x64') {
electronBuilderArgs.push('--x64', '--linux')
sharpPlatform = 'linux'
sharpArch = 'x64'
} else if (platform === 'linux-arm7') {
electronBuilderArgs.push('--armv7l', '--linux')
sharpPlatform = 'linux'
sharpArch = 'arm'
} else {
console.error('Unknown platform')
process.exit(1)
}
}

// Ensure we have the correct sharp libs
let sharpArgs = []
if (sharpPlatform) sharpArgs.push(`npm_config_platform=${sharpPlatform}`)
if (sharpArch) sharpArgs.push(`npm_config_arch=${sharpArch}`)
await $`cross-env ${sharpArgs} yarn dist:prepare:sharp`

const sharpVendorDir = './node_modules/sharp/vendor/'
const sharpVersionDirs = await fs.readdir(sharpVendorDir)
if (sharpVersionDirs.length !== 1) {
console.error(`Failed to determine sharp lib version`)
process.exit(1)
}

const sharpPlatformDirs = await fs.readdir(path.join(sharpVendorDir, sharpVersionDirs[0]))
if (sharpPlatformDirs.length !== 1) {
console.error(`Failed to determine sharp lib platform`)
process.exit(1)
}

const vipsVendorName = path.join(sharpVersionDirs[0], sharpPlatformDirs[0])
process.env.VIPS_VENDOR = vipsVendorName

// HACK: skip this as it is trying to rebuild everything from source and failing
// if (!platform) {
// // If for our own platform, make sure the correct deps are installed
Expand Down
Loading

0 comments on commit 3c01b55

Please sign in to comment.