mirror of
https://github.com/captbaritone/webamp.git
synced 2026-07-21 09:09:01 +00:00
Draw eq graph as pixels
This commit is contained in:
parent
9abd9a9e36
commit
683cd69582
3 changed files with 218 additions and 29 deletions
|
|
@ -1,10 +1,14 @@
|
|||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { getCurvePoints } from "cardinal-spline-js";
|
||||
import { getCurvePoints } from "./spline";
|
||||
import line from "./bresenham";
|
||||
import { percentToRange, clamp } from "../../utils";
|
||||
|
||||
import { BANDS } from "../../constants";
|
||||
|
||||
const GRAPH_HEIGHT = 19;
|
||||
const GRAPH_WIDTH = 113;
|
||||
|
||||
class EqGraph extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
|
@ -13,9 +17,8 @@ class EqGraph extends React.Component {
|
|||
|
||||
componentDidMount() {
|
||||
this.canvasCtx = this.canvas.getContext("2d");
|
||||
this.canvasCtx.imageSmoothingEnabled = false;
|
||||
this.width = this.canvas.width * 1; // Cast to int
|
||||
this.height = this.canvas.height * 1; // Cast to int
|
||||
this.width = Number(this.canvas.width);
|
||||
this.height = Number(this.canvas.height);
|
||||
|
||||
if (this.props.lineColorsImage) {
|
||||
this.createColorPattern(this.props.lineColorsImage);
|
||||
|
|
@ -52,17 +55,12 @@ class EqGraph extends React.Component {
|
|||
createColorPattern(lineColorsImage) {
|
||||
const bgImage = new Image();
|
||||
bgImage.onload = () => {
|
||||
const { width, height } = bgImage;
|
||||
const colorsCanvas = document.createElement("canvas");
|
||||
const colorsCtx = colorsCanvas.getContext("2d");
|
||||
colorsCanvas.width = bgImage.width * 2;
|
||||
colorsCanvas.height = bgImage.height * 2;
|
||||
colorsCtx.drawImage(
|
||||
bgImage,
|
||||
0,
|
||||
0,
|
||||
colorsCanvas.width,
|
||||
colorsCanvas.height
|
||||
);
|
||||
colorsCanvas.width = width;
|
||||
colorsCanvas.height = height;
|
||||
colorsCtx.drawImage(bgImage, 0, 0, width, height);
|
||||
this.setState({
|
||||
colorPattern: this.canvasCtx.createPattern(colorsCanvas, "repeat-x")
|
||||
});
|
||||
|
|
@ -78,31 +76,42 @@ class EqGraph extends React.Component {
|
|||
const { props } = this;
|
||||
const amplitudes = BANDS.map(band => props[band]);
|
||||
|
||||
this.canvasCtx.strokeStyle = this.state.colorPattern;
|
||||
this.canvasCtx.lineWidth = 2;
|
||||
this.canvasCtx.beginPath();
|
||||
const paddingLeft = 4; // TODO: This should be 3
|
||||
this.canvasCtx.fillStyle = this.state.colorPattern;
|
||||
const paddingLeft = 2; // TODO: This should be 1.5
|
||||
|
||||
const min = 1;
|
||||
const max = 31;
|
||||
const min = 0;
|
||||
const max = GRAPH_HEIGHT - 1;
|
||||
|
||||
const points = amplitudes.reduce((prev, value, i) => {
|
||||
const percent = (100 - value) / 100;
|
||||
const y = percentToRange(percent, min, max);
|
||||
return prev.concat(paddingLeft + i * 16, y);
|
||||
const x = i * 12; // Each band is 12 pixels wide
|
||||
return prev.concat(x, y);
|
||||
}, []);
|
||||
|
||||
// Spline between points in order to create nice curves
|
||||
const tension = 0.5;
|
||||
const tension = 0.8;
|
||||
const resolution = 4; // Points in each segment
|
||||
const smoothPoints = getCurvePoints(points, tension, resolution);
|
||||
const smoothPointCoords = [];
|
||||
for (let i = 0; i < smoothPoints.length; i += 2) {
|
||||
// Splining can push peaks out of bounds. So we fudge them back in.
|
||||
const y = clamp(smoothPoints[i + 1], min, max);
|
||||
this.canvasCtx.lineTo(smoothPoints[i], y);
|
||||
const x = Math.round(smoothPoints[i]);
|
||||
const y = Math.round(clamp(smoothPoints[i + 1], min, max));
|
||||
smoothPointCoords.push({ x, y });
|
||||
}
|
||||
|
||||
this.canvasCtx.stroke();
|
||||
let prev = smoothPointCoords.shift();
|
||||
|
||||
smoothPointCoords.forEach(next => {
|
||||
for (const point of line(prev, next)) {
|
||||
// Note: Technially, we are double drawing each point given to us by
|
||||
// getCurvePoints, since the end of each line is the same as the start
|
||||
// of the next.
|
||||
this.canvasCtx.fillRect(paddingLeft + point.x, point.y, 1, 1);
|
||||
}
|
||||
prev = next;
|
||||
});
|
||||
}
|
||||
|
||||
drawPreampLine() {
|
||||
|
|
@ -111,13 +120,17 @@ class EqGraph extends React.Component {
|
|||
// The skin has not finished loading yet
|
||||
return;
|
||||
}
|
||||
const preampValue = percentToRange(this.props.preamp / 100, 0, 30);
|
||||
const preampValue = percentToRange(
|
||||
this.props.preamp / 100,
|
||||
0,
|
||||
GRAPH_HEIGHT - 1
|
||||
);
|
||||
this.canvasCtx.drawImage(
|
||||
this.state.preampLineImg,
|
||||
0,
|
||||
preampValue,
|
||||
preampLineImg.width * 2,
|
||||
preampLineImg.height * 2
|
||||
preampLineImg.width,
|
||||
preampLineImg.height
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -126,8 +139,8 @@ class EqGraph extends React.Component {
|
|||
<canvas
|
||||
id="eqGraph"
|
||||
ref={node => (this.canvas = node)}
|
||||
width="152"
|
||||
height="32"
|
||||
width={GRAPH_WIDTH}
|
||||
height={GRAPH_HEIGHT}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
71
js/components/EqualizerWindow/bresenham.ts
Normal file
71
js/components/EqualizerWindow/bresenham.ts
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
// Adapted from https://github.com/nquicenob/bresenham-line by Nicolas Quiceno
|
||||
interface Point {
|
||||
x: number;
|
||||
y: number;
|
||||
}
|
||||
|
||||
type Sign = 1 | -1;
|
||||
function getSing(num: number): Sign {
|
||||
return num > 0 ? 1 : -1;
|
||||
}
|
||||
|
||||
function getInitValues(startPoint: Point, finalPoint: Point) {
|
||||
const abs = Math.abs;
|
||||
const diffx = finalPoint.x - startPoint.x;
|
||||
const diffy = finalPoint.y - startPoint.y;
|
||||
|
||||
return {
|
||||
absDiff: {
|
||||
x: abs(diffx),
|
||||
y: abs(diffy)
|
||||
},
|
||||
sign: {
|
||||
x: getSing(diffx),
|
||||
y: getSing(diffy)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function getBreakFn(sign: Sign): (current: number, final: number) => boolean {
|
||||
return sign < 0
|
||||
? (current, final) => current >= final
|
||||
: (current, final) => current <= final;
|
||||
}
|
||||
|
||||
function calcMainCoordinates(absDiff: Point): ["x", "y"] | ["y", "x"] {
|
||||
return absDiff.x > absDiff.y ? ["x", "y"] : ["y", "x"];
|
||||
}
|
||||
|
||||
export default function line(point: Point, finalPoint: Point) {
|
||||
const { absDiff, sign } = getInitValues(point, finalPoint);
|
||||
const [mainCoordinate, coordinate] = calcMainCoordinates(absDiff);
|
||||
|
||||
const final = finalPoint[mainCoordinate];
|
||||
|
||||
const mainSign = sign[mainCoordinate];
|
||||
const secondSign = sign[coordinate];
|
||||
|
||||
const mainDiff = absDiff[mainCoordinate];
|
||||
const secondDiff = absDiff[coordinate];
|
||||
|
||||
const breakFn = getBreakFn(mainSign);
|
||||
|
||||
let mainValue = point[mainCoordinate];
|
||||
let secondValue = point[coordinate];
|
||||
|
||||
let eps = 0;
|
||||
|
||||
const points = [];
|
||||
for (; breakFn(mainValue, final); mainValue += mainSign) {
|
||||
points.push({
|
||||
[mainCoordinate]: mainValue,
|
||||
[coordinate]: secondValue
|
||||
});
|
||||
eps += secondDiff;
|
||||
if (eps << 1 >= mainDiff) {
|
||||
secondValue += secondSign;
|
||||
eps -= mainDiff;
|
||||
}
|
||||
}
|
||||
return points;
|
||||
}
|
||||
105
js/components/EqualizerWindow/spline.js
Normal file
105
js/components/EqualizerWindow/spline.js
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
/*! Curve calc function for canvas 2.3.1
|
||||
* Epistemex (c) 2013-2014
|
||||
* License: MIT
|
||||
*/
|
||||
|
||||
/**
|
||||
* Calculates an array containing points representing a cardinal spline through given point array.
|
||||
* Points must be arranged as: [x1, y1, x2, y2, ..., xn, yn].
|
||||
*
|
||||
* The points for the cardinal spline are returned as a new array.
|
||||
*
|
||||
* @param {Array} points - point array
|
||||
* @param {Number} [tension=0.5] - tension. Typically between [0.0, 1.0] but can be exceeded
|
||||
* @param {Number} [numOfSeg=20] - number of segments between two points (line resolution)
|
||||
* @param {Boolean} [close=false] - Close the ends making the line continuous
|
||||
* @returns {Float32Array} New array with the calculated points that was added to the path
|
||||
*/
|
||||
export function getCurvePoints(points, tension, numOfSeg, close) {
|
||||
"use strict";
|
||||
|
||||
// options or defaults
|
||||
tension = typeof tension === "number" ? tension : 0.5;
|
||||
numOfSeg = numOfSeg ? numOfSeg : 25;
|
||||
|
||||
var pts, // for cloning point array
|
||||
i = 1,
|
||||
l = points.length,
|
||||
rPos = 0,
|
||||
rLen = (l - 2) * numOfSeg + 2 + (close ? 2 * numOfSeg : 0),
|
||||
res = new Float32Array(rLen),
|
||||
cache = new Float32Array((numOfSeg + 2) * 4),
|
||||
cachePtr = 4;
|
||||
|
||||
pts = points.slice(0);
|
||||
|
||||
if (close) {
|
||||
pts.unshift(points[l - 1]); // insert end point as first point
|
||||
pts.unshift(points[l - 2]);
|
||||
pts.push(points[0], points[1]); // first point as last point
|
||||
} else {
|
||||
pts.unshift(points[1]); // copy 1. point and insert at beginning
|
||||
pts.unshift(points[0]);
|
||||
pts.push(points[l - 2], points[l - 1]); // duplicate end-points
|
||||
}
|
||||
|
||||
// cache inner-loop calculations as they are based on t alone
|
||||
cache[0] = 1; // 1,0,0,0
|
||||
|
||||
for (; i < numOfSeg; i++) {
|
||||
var st = i / numOfSeg,
|
||||
st2 = st * st,
|
||||
st3 = st2 * st,
|
||||
st23 = st3 * 2,
|
||||
st32 = st2 * 3;
|
||||
|
||||
cache[cachePtr++] = st23 - st32 + 1; // c1
|
||||
cache[cachePtr++] = st32 - st23; // c2
|
||||
cache[cachePtr++] = st3 - 2 * st2 + st; // c3
|
||||
cache[cachePtr++] = st3 - st2; // c4
|
||||
}
|
||||
|
||||
cache[++cachePtr] = 1; // 0,1,0,0
|
||||
|
||||
// calc. points
|
||||
parse(pts, cache, l);
|
||||
|
||||
if (close) {
|
||||
//l = points.length;
|
||||
pts = [];
|
||||
pts.push(points[l - 4], points[l - 3], points[l - 2], points[l - 1]); // second last and last
|
||||
pts.push(points[0], points[1], points[2], points[3]); // first and second
|
||||
parse(pts, cache, 4);
|
||||
}
|
||||
|
||||
function parse(pts, cache, l) {
|
||||
for (var i = 2, t; i < l; i += 2) {
|
||||
var pt1 = pts[i],
|
||||
pt2 = pts[i + 1],
|
||||
pt3 = pts[i + 2],
|
||||
pt4 = pts[i + 3],
|
||||
t1x = (pt3 - pts[i - 2]) * tension,
|
||||
t1y = (pt4 - pts[i - 1]) * tension,
|
||||
t2x = (pts[i + 4] - pt1) * tension,
|
||||
t2y = (pts[i + 5] - pt2) * tension;
|
||||
|
||||
for (t = 0; t < numOfSeg; t++) {
|
||||
var c = t << 2, //t * 4;
|
||||
c1 = cache[c],
|
||||
c2 = cache[c + 1],
|
||||
c3 = cache[c + 2],
|
||||
c4 = cache[c + 3];
|
||||
|
||||
res[rPos++] = c1 * pt1 + c2 * pt3 + c3 * t1x + c4 * t2x;
|
||||
res[rPos++] = c1 * pt2 + c2 * pt4 + c3 * t1y + c4 * t2y;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// add last point
|
||||
l = close ? 0 : points.length - 2;
|
||||
res[rPos++] = points[l];
|
||||
res[rPos] = points[l + 1];
|
||||
|
||||
return res;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue